diff --git a/dm/dm/config/task.go b/dm/dm/config/task.go
index 6aee7325ee5..cbf7dc2fbd9 100644
--- a/dm/dm/config/task.go
+++ b/dm/dm/config/task.go
@@ -580,8 +580,10 @@ func (c *TaskConfig) adjust() error {
 				return terror.ErrConfigMydumperCfgNotFound.Generate(i, inst.MydumperConfigName)
 			}
 			globalConfigReferCount[configRefPrefixes[mydumperIdx]+inst.MydumperConfigName]++
-			inst.Mydumper = new(MydumperConfig)
-			*inst.Mydumper = *rule // ref mydumper config
+			if rule != nil {
+				inst.Mydumper = new(MydumperConfig)
+				*inst.Mydumper = *rule // ref mydumper config
+			}
 		}
 		if inst.Mydumper == nil {
 			if len(c.Mydumpers) != 0 {
@@ -608,8 +610,10 @@ func (c *TaskConfig) adjust() error {
 				return terror.ErrConfigLoaderCfgNotFound.Generate(i, inst.LoaderConfigName)
 			}
 			globalConfigReferCount[configRefPrefixes[loaderIdx]+inst.LoaderConfigName]++
-			inst.Loader = new(LoaderConfig)
-			*inst.Loader = *rule // ref loader config
+			if rule != nil {
+				inst.Loader = new(LoaderConfig)
+				*inst.Loader = *rule // ref loader config
+			}
 		}
 		if inst.Loader == nil {
 			if len(c.Loaders) != 0 {
@@ -628,8 +632,10 @@ func (c *TaskConfig) adjust() error {
 				return terror.ErrConfigSyncerCfgNotFound.Generate(i, inst.SyncerConfigName)
 			}
 			globalConfigReferCount[configRefPrefixes[syncerIdx]+inst.SyncerConfigName]++
-			inst.Syncer = new(SyncerConfig)
-			*inst.Syncer = *rule // ref syncer config
+			if rule != nil {
+				inst.Syncer = new(SyncerConfig)
+				*inst.Syncer = *rule // ref syncer config
+			}
 		}
 		if inst.Syncer == nil {
 			if len(c.Syncers) != 0 {
@@ -642,6 +648,21 @@ func (c *TaskConfig) adjust() error {
 			inst.Syncer.WorkerCount = inst.SyncerThread
 		}
 
+<<<<<<< HEAD
+=======
+		inst.ContinuousValidator = defaultValidatorConfig()
+		if inst.ContinuousValidatorConfigName != "" {
+			rule, ok := c.Validators[inst.ContinuousValidatorConfigName]
+			if !ok {
+				return terror.ErrContinuousValidatorCfgNotFound.Generate(i, inst.ContinuousValidatorConfigName)
+			}
+			globalConfigReferCount[configRefPrefixes[validatorIdx]+inst.ContinuousValidatorConfigName]++
+			if rule != nil {
+				inst.ContinuousValidator = *rule
+			}
+		}
+
+>>>>>>> 21608dce2 (dm: fix empty config cause dm-master panic (#5298))
 		// for backward compatible, set global config `ansi-quotes: true` if any syncer is true
 		if inst.Syncer.EnableANSIQuotes {
 			log.L().Warn("DM could discover proper ANSI_QUOTES, `enable-ansi-quotes` is no longer take effect")
@@ -695,7 +716,17 @@ func (c *TaskConfig) adjust() error {
 			unusedConfigs = append(unusedConfigs, mydumper)
 		}
 	}
+<<<<<<< HEAD
 	for loader := range c.Loaders {
+=======
+
+	for loader, cfg := range c.Loaders {
+		if cfg != nil {
+			if err1 := cfg.adjust(); err1 != nil {
+				return err1
+			}
+		}
+>>>>>>> 21608dce2 (dm: fix empty config cause dm-master panic (#5298))
 		if globalConfigReferCount[configRefPrefixes[loaderIdx]+loader] == 0 {
 			unusedConfigs = append(unusedConfigs, loader)
 		}
@@ -715,6 +746,7 @@ func (c *TaskConfig) adjust() error {
 		sort.Strings(unusedConfigs)
 		return terror.ErrConfigGlobalConfigsUnused.Generate(unusedConfigs)
 	}
+
 	// we postpone default time_zone init in each unit so we won't change the config value in task/sub_task config
 	if c.Timezone != "" {
 		if _, err := utils.ParseTimeZone(c.Timezone); err != nil {
diff --git a/dm/tests/dmctl_basic/check_list/check_task.sh b/dm/tests/dmctl_basic/check_list/check_task.sh
index 6f2200459b2..4845b5a9b13 100644
--- a/dm/tests/dmctl_basic/check_list/check_task.sh
+++ b/dm/tests/dmctl_basic/check_list/check_task.sh
@@ -64,3 +64,41 @@ function check_task_error_count() {
 		"\"failed\": 2" 1 \
 		"\"state\": \"fail\"" 0
 }
+<<<<<<< HEAD
+=======
+
+function check_task_only_warning() {
+	task_conf=$1
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"check-task $task_conf" \
+		"\"state\": \"warn\"" 1
+}
+
+function check_task_empty_dump_config() {
+	sed "/threads/d" $1 >/tmp/empty-dump.yaml
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"check-task /tmp/empty-dump.yaml" \
+		"pre-check is passed" 1
+}
+
+function check_task_empty_load_config() {
+	sed "/pool-size/d" $1 >/tmp/empty-load.yaml
+	cat /tmp/empty-load.yaml
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"check-task /tmp/empty-load.yaml" \
+		"pre-check is passed" 1
+}
+
+function check_task_empty_sync_config() {
+	sed "/worker-count/d" $1 >/tmp/empty-sync.yaml
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"check-task /tmp/empty-sync.yaml" \
+		"pre-check is passed" 1
+}
+
+function check_task_empty_config() {
+	check_task_empty_dump_config $1
+	check_task_empty_load_config $1
+	check_task_empty_sync_config $1
+}
+>>>>>>> 21608dce2 (dm: fix empty config cause dm-master panic (#5298))
diff --git a/dm/tests/dmctl_basic/check_list/start_task.sh b/dm/tests/dmctl_basic/check_list/start_task.sh
index acbb79cdf3c..7a51dd87702 100644
--- a/dm/tests/dmctl_basic/check_list/start_task.sh
+++ b/dm/tests/dmctl_basic/check_list/start_task.sh
@@ -11,3 +11,39 @@ function start_task_wrong_config_file() {
 		"start-task not_exists_config_file" \
 		"error in get file content" 1
 }
+<<<<<<< HEAD
+=======
+
+function start_task_wrong_start_time_format() {
+	task_conf=$1
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"start-task $task_conf --start-time '20060102 150405'" \
+		"error while parse start-time" 1
+}
+
+function start_task_not_pass_with_message() {
+	task_conf=$1
+	error_message=$2
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"start-task $task_conf" \
+		"$2" 1
+}
+
+function start_task_empty_config() {
+	cp $1 /tmp/empty-cfg.yaml
+	sed -i "/threads/d" /tmp/empty-cfg.yaml
+	sed -i "/pool-size/d" /tmp/empty-cfg.yaml
+	sed -i "/worker-count/d" /tmp/empty-cfg.yaml
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"start-task /tmp/empty-cfg.yaml" \
+		"\"result\": true" 2
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"config task empty-unit-task" \
+		"threads: 4" 1 \
+		"pool-size: 16" 1 \
+		"worker-count: 16" 1
+	run_dm_ctl $WORK_DIR "127.0.0.1:$MASTER_PORT" \
+		"stop-task $1" \
+		"\"result\": true" 2
+}
+>>>>>>> 21608dce2 (dm: fix empty config cause dm-master panic (#5298))
diff --git a/dm/tests/dmctl_basic/conf/empty-unit-task.yaml b/dm/tests/dmctl_basic/conf/empty-unit-task.yaml
new file mode 100644
index 00000000000..2eb41893d3d
--- /dev/null
+++ b/dm/tests/dmctl_basic/conf/empty-unit-task.yaml
@@ -0,0 +1,35 @@
+---
+name: empty-unit-task
+task-mode: all
+
+target-database:
+  host: "127.0.0.1"
+  port: 4000
+  user: "root"
+  password: ""
+
+mysql-instances:
+  - source-id: "mysql-replica-01"
+    mydumper-config-name: "global"
+    loader-config-name: "global"
+    syncer-config-name: "global"
+    block-allow-list:  "instance"
+
+mydumpers:
+  global:
+    threads: 4
+
+loaders:
+  global:
+    pool-size: 16
+
+syncers:
+  global:
+    worker-count: 32
+
+block-allow-list:
+  instance:
+    do-dbs: ["dmctl"]
+    do-tables:
+    -  db-name: "dmctl"
+       tbl-name: "~^t_[\\d]+"
\ No newline at end of file
diff --git a/dm/tests/dmctl_basic/run.sh b/dm/tests/dmctl_basic/run.sh
index 87ce61a95c1..649cdeb93db 100755
--- a/dm/tests/dmctl_basic/run.sh
+++ b/dm/tests/dmctl_basic/run.sh
@@ -282,6 +282,11 @@ function run() {
 	check_task_not_pass $cur/conf/dm-task2.yaml
 	check_task_error_count $cur/conf/dm-task3.yaml
 
+	echo "check task with empty unit config"
+	check_task_empty_config $cur/conf/empty-unit-task.yaml
+	echo "start task with empty unit config"
+	start_task_empty_config $cur/conf/empty-unit-task.yaml
+
 	cp $cur/conf/dm-task.yaml $WORK_DIR/dm-task-error-database-config.yaml
 	sed -i "s/password: \"\"/password: \"wrond password\"/g" $WORK_DIR/dm-task-error-database-config.yaml
 	check_task_error_database_config $WORK_DIR/dm-task-error-database-config.yaml