diff -up etcd-3.4.3/etcdmain/config.go.orig etcd-3.4.3/etcdmain/config.go
--- etcd-3.4.3/etcdmain/config.go.orig	2019-10-23 19:11:46.000000000 +0200
+++ etcd-3.4.3/etcdmain/config.go	2020-02-06 01:16:58.443641282 +0100
@@ -123,14 +123,15 @@ func newConfig() *config {
 		),
 	}
 
-	fs := cfg.cf.flagSet
-	fs.Usage = func() {
+	fs := InitFlagSet(cfg.cf.flagSet)
+	cfg.cf.flagSet.Usage = func() {
 		fmt.Fprintln(os.Stderr, usageline)
 	}
 
 	fs.StringVar(&cfg.configFile, "config-file", "", "Path to the server configuration file. Note that if a configuration file is provided, other command line flags and environment variables will be ignored.")
 
 	// member
+	fs.AddGroup("member")
 	fs.StringVar(&cfg.ec.Dir, "data-dir", cfg.ec.Dir, "Path to the data directory.")
 	fs.StringVar(&cfg.ec.WalDir, "wal-dir", cfg.ec.WalDir, "Path to the dedicated wal directory.")
 	fs.Var(
@@ -163,7 +164,7 @@ func newConfig() *config {
 	fs.DurationVar(&cfg.ec.GRPCKeepAliveInterval, "grpc-keepalive-interval", cfg.ec.GRPCKeepAliveInterval, "Frequency duration of server-to-client ping to check if a connection is alive (0 to disable).")
 	fs.DurationVar(&cfg.ec.GRPCKeepAliveTimeout, "grpc-keepalive-timeout", cfg.ec.GRPCKeepAliveTimeout, "Additional duration of wait before closing a non-responsive connection (0 to disable).")
 
-	// clustering
+	fs.AddGroup("clustering")
 	fs.Var(
 		flags.NewUniqueURLsWithExceptions(embed.DefaultInitialAdvertisePeerURLs, ""),
 		"initial-advertise-peer-urls",
@@ -188,7 +189,7 @@ func newConfig() *config {
 	fs.BoolVar(&cfg.ec.EnableV2, "enable-v2", cfg.ec.EnableV2, "Accept etcd V2 client requests.")
 	fs.BoolVar(&cfg.ec.PreVote, "pre-vote", cfg.ec.PreVote, "Enable to run an additional Raft election phase.")
 
-	// proxy
+	fs.AddGroup("proxy")
 	fs.Var(cfg.cf.proxy, "proxy", fmt.Sprintf("Valid values include %q", cfg.cf.proxy.Valids()))
 	fs.UintVar(&cfg.cp.ProxyFailureWaitMs, "proxy-failure-wait", cfg.cp.ProxyFailureWaitMs, "Time (in milliseconds) an endpoint will be held in a failed state.")
 	fs.UintVar(&cfg.cp.ProxyRefreshIntervalMs, "proxy-refresh-interval", cfg.cp.ProxyRefreshIntervalMs, "Time (in milliseconds) of the endpoints refresh interval.")
@@ -196,7 +197,7 @@ func newConfig() *config {
 	fs.UintVar(&cfg.cp.ProxyWriteTimeoutMs, "proxy-write-timeout", cfg.cp.ProxyWriteTimeoutMs, "Time (in milliseconds) for a write to timeout.")
 	fs.UintVar(&cfg.cp.ProxyReadTimeoutMs, "proxy-read-timeout", cfg.cp.ProxyReadTimeoutMs, "Time (in milliseconds) for a read to timeout.")
 
-	// security
+	fs.AddGroup("security")
 	fs.StringVar(&cfg.ec.ClientTLSInfo.CertFile, "cert-file", "", "Path to the client server TLS cert file.")
 	fs.StringVar(&cfg.ec.ClientTLSInfo.KeyFile, "key-file", "", "Path to the client server TLS key file.")
 	fs.BoolVar(&cfg.ec.ClientTLSInfo.ClientCertAuth, "client-cert-auth", false, "Enable client cert authentication.")
@@ -222,7 +223,7 @@ func newConfig() *config {
 	)
 	fs.Var(flags.NewUniqueStringsValue("*"), "host-whitelist", "Comma-separated acceptable hostnames from HTTP client requests, if server is not secure (empty means allow all).")
 
-	// logging
+	fs.AddGroup("logging")
 	fs.StringVar(&cfg.ec.Logger, "logger", "capnslog", "Specify 'zap' for structured logging or 'capnslog'. WARN: 'capnslog' is being deprecated in v3.5.")
 	fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-output", "[TO BE DEPRECATED IN v3.5] use '--log-outputs'.")
 	fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-outputs", "Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.")
@@ -230,26 +231,26 @@ func newConfig() *config {
 	fs.StringVar(&cfg.ec.LogLevel, "log-level", logutil.DefaultLogLevel, "Configures log level. Only supports debug, info, warn, error, panic, or fatal. Default 'info'.")
 	fs.StringVar(&cfg.ec.LogPkgLevels, "log-package-levels", "", "[TO BE DEPRECATED IN v3.5] Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').")
 
-	// version
+	fs.AddGroup("version")
 	fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.")
 
 	fs.StringVar(&cfg.ec.AutoCompactionRetention, "auto-compaction-retention", "0", "Auto compaction retention for mvcc key value store. 0 means disable auto compaction.")
 	fs.StringVar(&cfg.ec.AutoCompactionMode, "auto-compaction-mode", "periodic", "interpret 'auto-compaction-retention' one of: periodic|revision. 'periodic' for duration based retention, defaulting to hours if no time unit is provided (e.g. '5m'). 'revision' for revision number based retention.")
 
-	// pprof profiler via HTTP
+	fs.AddGroup("profiling")
 	fs.BoolVar(&cfg.ec.EnablePprof, "enable-pprof", false, "Enable runtime profiling data via HTTP server. Address is at client URL + \"/debug/pprof/\"")
 
 	// additional metrics
 	fs.StringVar(&cfg.ec.Metrics, "metrics", cfg.ec.Metrics, "Set level of detail for exported metrics, specify 'extensive' to include histogram metrics")
 
-	// auth
+	fs.AddGroup("auth")
 	fs.StringVar(&cfg.ec.AuthToken, "auth-token", cfg.ec.AuthToken, "Specify auth token specific options.")
 	fs.UintVar(&cfg.ec.BcryptCost, "bcrypt-cost", cfg.ec.BcryptCost, "Specify bcrypt algorithm cost factor for auth password hashing.")
 
-	// gateway
+	fs.AddGroup("gateway")
 	fs.BoolVar(&cfg.ec.EnableGRPCGateway, "enable-grpc-gateway", true, "Enable GRPC gateway.")
 
-	// experimental
+	fs.AddGroup("experimental")
 	fs.BoolVar(&cfg.ec.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ec.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.")
 	fs.DurationVar(&cfg.ec.ExperimentalCorruptCheckTime, "experimental-corrupt-check-time", cfg.ec.ExperimentalCorruptCheckTime, "Duration of time between cluster corruption check passes.")
 	fs.StringVar(&cfg.ec.ExperimentalEnableV2V3, "experimental-enable-v2v3", cfg.ec.ExperimentalEnableV2V3, "v3 prefix for serving emulated v2 state.")
@@ -257,9 +258,12 @@ func newConfig() *config {
 	fs.BoolVar(&cfg.ec.ExperimentalEnableLeaseCheckpoint, "experimental-enable-lease-checkpoint", false, "Enable to persist lease remaining TTL to prevent indefinite auto-renewal of long lived leases.")
 	fs.IntVar(&cfg.ec.ExperimentalCompactionBatchLimit, "experimental-compaction-batch-limit", cfg.ec.ExperimentalCompactionBatchLimit, "Sets the maximum revisions deleted in each compaction batch.")
 
-	// unsafe
+	fs.AddGroup("unsafe")
 	fs.BoolVar(&cfg.ec.ForceNewCluster, "force-new-cluster", false, "Force to create a new one member cluster.")
 
+	fs.GenMan()
+	os.Exit(0)
+
 	// ignored
 	for _, f := range cfg.ignored {
 		fs.Var(&flags.IgnoredFlag{Name: f}, f, "")
diff -up etcd-3.4.3/etcdmain/fake_flagset.go.orig etcd-3.4.3/etcdmain/fake_flagset.go
--- etcd-3.4.3/etcdmain/fake_flagset.go.orig	2020-02-06 01:14:12.639989665 +0100
+++ etcd-3.4.3/etcdmain/fake_flagset.go	2020-02-06 01:14:12.639989665 +0100
@@ -0,0 +1,157 @@
+package etcdmain
+
+import (
+	"flag"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type flagInfo struct {
+	value fmt.Stringer
+	name  string
+	usage string
+}
+
+type stringValue string
+
+func (s *stringValue) String() string { return string(*s) }
+
+type uintValue uint
+
+func (i *uintValue) String() string { return strconv.FormatUint(uint64(*i), 10) }
+
+type uint64Value uint64
+
+func (i *uint64Value) String() string { return strconv.FormatUint(uint64(*i), 10) }
+
+type int64Value int64
+
+func (i *int64Value) String() string { return strconv.FormatInt(int64(*i), 10) }
+
+type durationValue time.Duration
+
+func (d *durationValue) String() string { return (*time.Duration)(d).String() }
+
+type boolValue bool
+
+func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) }
+
+type FakeFlagSet struct {
+	fs        *flag.FlagSet
+	flags     map[string][]flagInfo
+	groups    []string
+	lastGroup string
+}
+
+func InitFlagSet(fs *flag.FlagSet) *FakeFlagSet {
+	return &FakeFlagSet{
+		fs:    fs,
+		flags: make(map[string][]flagInfo),
+	}
+}
+
+func (fs *FakeFlagSet) AddGroup(name string) {
+	fs.flags[name] = nil
+	fs.groups = append(fs.groups, name)
+	fs.lastGroup = name
+}
+
+func (fs *FakeFlagSet) Var(value flag.Value, name string, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: value,
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.Var(value, name, usage)
+}
+
+func (fs *FakeFlagSet) StringVar(p *string, name string, value string, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*stringValue)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.StringVar(p, name, value, usage)
+}
+
+// -- int Value
+type intValue int
+
+func (i *intValue) String() string { return strconv.Itoa(int(*i)) }
+
+func (fs *FakeFlagSet) UintVar(p *uint, name string, value uint, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*uintValue)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.UintVar(p, name, value, usage)
+}
+
+func (fs *FakeFlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*uint64Value)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.Uint64Var(p, name, value, usage)
+}
+
+func (fs *FakeFlagSet) IntVar(p *int, name string, value int, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*intValue)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.IntVar(p, name, value, usage)
+}
+
+func (fs *FakeFlagSet) Int64Var(p *int64, name string, value int64, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*int64Value)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.Int64Var(p, name, value, usage)
+}
+
+func (fs *FakeFlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*durationValue)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.DurationVar(p, name, value, usage)
+}
+
+func (fs *FakeFlagSet) BoolVar(p *bool, name string, value bool, usage string) {
+	fs.flags[fs.lastGroup] = append(fs.flags[fs.lastGroup], flagInfo{
+		value: (*boolValue)(&value),
+		name:  name,
+		usage: usage,
+	})
+	fs.fs.BoolVar(p, name, value, usage)
+}
+
+func (fs *FakeFlagSet) GenMan() {
+	fmt.Printf(".TH \"ETCD\" \"1\" \" etcd User Manuals\" \"Etcd contributors\" \"Nov 2017\"  \"\"\n")
+	fmt.Printf(".SH NAME:\netcd - Distributed reliable key-value store for the most critical data of a distributed system\n\n")
+	fmt.Printf(".SH USAGE:\netcd [flags]\n\n")
+	fmt.Printf(".SH DESCRIPTION:\nEtcd is a distributed key-value store designed to reliably and quickly preserve and provide access to critical data. It enables reliable distributed coordination through distributed locking, leader elections, and write barriers. An etcd cluster is intended for high availability and permanent data storage and retrieval.\n\n")
+	fmt.Printf(".SH GENERAL OPTIONS\n\n")
+	for _, group := range fs.groups {
+		fmt.Printf(".I %v flags\n\n", strings.Title(group))
+		for _, flag := range fs.flags[group] {
+			var flagstr string
+			if len(flag.name) == 1 {
+				flagstr = "-" + flag.name
+			} else {
+				flagstr = "--" + flag.name
+			}
+			fmt.Printf(".RS\n\\fB%v %v\\fP\n      %v\n\n.RE\n", flagstr, flag.value.String(), flag.usage)
+		}
+	}
+	fmt.Printf(".SH SEE ALSO:\n\\fBetcdctl(1)\\fP, \\fBetcdctl2(1)\\fP, \\fBetcdctl3(1)\\fP\n\n")
+}
