前言
因为要搭环境复现漏洞等一系列原因,所以这里还是得把 Docker 学起来
参考:
官方文档: https://docs.docker.com/
https://treasure-house.randark.site/docs/DevSecOps/Containerization/Docker/Dockerfile-guide
https://yeasy.gitbook.io/docker_practice/container/attach_exec
介绍
by 菜鸟教程
Docker 是一个用于开发,交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开,从而可以快速交付软件。借助 Docker,您可以与管理应用程序相同的方式来管理基础架构。通过利用 Docker 的方法来快速交付,测试和部署代码,您可以大大减少编写代码和在生产环境中运行代码之间的延迟。
引用这里参考csdn文章的博主的理解:
1.可以快速搭建起程序运行所需要的环境。
2.可以打包程序和运行环境,避免因为缺乏某些库、或是环境变量设置的问题等导致程序无法运行。也就是说,解决了为什么在我的电脑上可以运行而在其他人电脑上不行的问题。
3.Docker只有需要的环境,比虚拟机占用的资源更少,操作也比虚拟机更简单。
安装
本人是在windows WSL2 上进行配置的
主要的安装流程可移步至菜鸟教程
这里提几个小tips
更改数据存储位置
windows默认安装在C盘,希望盘没逝
停止Docker服务
导出,备份数据到另一个文件夹
wsl --export docker-desktop-data "E:\Docker\docker-desktop-data.tar"删除原有数据
wsl --unregister docker-desktop-data更改数据存储盘并恢复数据
wsl --import docker-desktop-data "E:\Docker" "E:\Docker\docker-desktop-data.tar" --version 2即将备份数据导入到新的虚拟盘,且指定虚拟盘的存放路径为E:\Docker,导入完成后在该目录下会存在一个ext4.vhdx的虚拟磁盘路径

然后直接启动docker就行
更改镜像源
经典切换国内镜像
在设置的Docker Engine添加以下内容
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"http://f1361db2.m.daocloud.io",
"https://registry.docker-cn.com"
],
"insecure-registries": [],
"debug": true,
"experimental": false

阿里云要注册账号,这里就不演示了(
运行
验证安装是否成功
cmd里执行命令
docker version

出现这些信息则说明安装完成
查看镜像
docker images

REPOSITY代表所在仓库,TAG表示该镜像标签(版本)
对应的Docker Desktop图形化界面

查看所有容器
docker ps -a

CONTAINER ID表示该容器在Docker中的唯一ID,稍后的相关操作可以使用该ID进行,IMAGES表示该容器使用的镜像,NAMES表示我们给当前容器起的花名,与ID一样是唯一的
对应的Docker Desktop图形化界面

拉取镜像并启动容器
从 docker 基础中可以了解到,docker 容器其实就是拉取相应的镜像并启动它,那就可以理解成虚拟机,但与虚拟机相比优势更大,可以把这个容器便是一个基于 Linux 的独立的”操作系统”了
这里以hello-world镜像为例
拉取镜像
docker pull hello-world[:TAGS]
这样就算拉取成功了
对应的Docker Desktop图形化界面:
搜索栏中查找,点击Pull即可

接着可以使用
docker images或者直接在Docker Desktop图形化界面中查看本机中所有的镜像最后启动一个容器实例
docker run -itd --name test hello-world /hello
返回了该容器的全称CONTAINER ID
其中
-itd表示以交互式终端切后台运行的模式启动即启动后容器仅在后台运行,不会进入容器实例–name test表示给该容器自定义的名字hello-world表示使用的镜像/hello对应执行的命令,交互式shell可以是/bin/bash如果想进入该容器可以使用命令(如Ubuntu这类可以进入的镜像)
docker exec -it 【CONTAINER ID】 /bin/bash注:在第三步执行的时候倘若还没有镜像则会自动拉取镜像
CONTAINER ID可以少几位,会自己识别
运行镜像
docker run hello-world

说明运行成功,这个方法会创建一个新的容器
守护态运行
更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现
$ docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
$ docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
不加 -d 的情况会使容器把 STDOUT 打印到宿主机上,使用 -d 后要查看的话可以用 docker logs
镜像操作
docker rm <container_id_or_name>
docker rmi [OPTIONS] IMAGE [IMAGE...]
docker save -o image.tar image:latest # 保存镜像
docker load < image.tar
进入容器
有两种方法进入,attach 和 exec
$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
243c32535da7 ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds nostalgic_hypatia
$ docker attach 243c
root@243c32535da7:/#
使用 attach 的情况下,如果从这个 stdin 中 exit,会导致容器的停止
使用 exec 则不会
$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69d137adef7a ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds zealous_swirles
$ docker exec -i 69d1 bash
ls
bin
boot
dev
...
$ docker exec -it 69d1 bash
root@69d137adef7a:/#
挂载目录
命令行中查看容器挂载的主机目录
docker inspect 容器ID或容器名 | grep -A 30 Mounts
基于CTF出题
套模板
对于基础的 PHP 题目而言,我们可以通过拉取已有的 lamp 容器的方式,替换其中的 php 文件即可
记录了一次出题过程:
拉取镜像
先拉一个题目环境下来
这里拉了一个vaalacat/push_f12下来
启动镜像
出题
先在本地编写题目代码
<!DOCTYPE html>
<html>
<head>
<title>FTwelve</title>
<!--flag{ctf web St@rt!}-->
</head>
<body>
<h1>这是什么?</h1>
<p>你说的对,但是web是由...</p>
<p>中间忘了</p>
<p>后面忘了</p>
<p>总之就是逐步发掘phpinfo()的真相(</p>
<!--Huh?-->
<?php
phpinfo();
?>
</body>
</html>
然后拷贝到容器中
docker cp ./ 35007d4:/var/www/html

封装镜像
把这个容器封装成一个新的镜像
docker commit 35007d my_f12
上传到自己的Hub
注册一个账号
然后
docker tag my_f12 c1oudfl0w0/my_f12:tags
然后就能 push 到自己的仓库了
dockerfile
Dockerfile 文件本身可以看作是编译控制流的一种,通过相关语句可以同时最终 docker 镜像的编译成果。在 Dockerfile 中,可以实现命令执行,文件传输,环境变量控制,标签控制,容器入口点控制等等
# 文件传递
ADD
COPY
# 命令执行
RUN
# 容器入口点控制
CMD
ENTRYPOINT
# 标签控制
TAG
docker-compose
主要用于定义和运行多容器 Docker 应用程序
参考: https://docs.docker.com/compose/gettingstarted/
命令:
docker compose up
docker compose up -d # 后台启动所有服务
docker compose stop
docker compose down # 停止并删除所有服务容器、网络和卷
Quickstart
LAMP
注意:php 8.1-apache 及之后的版本换源的位置迁移到了 /etc/apt/sources.list.d/debian.sources;php 8.0-apache 及之前到版本换源的位置在 /etc/apt/sources.list
python+redis
搭建一个 python + redis 的服务,把本地的 app.py 映射到容器中方便修改
app/app.py
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return f'Hello World! I have been seen {count} times.\n'
app/requirements.txt
flask
redis
app/Dockerfile
FROM python:3.10-alpine
WORKDIR /app
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run", "--debug"]
docker-compose.yml
services:
web:
build: ./app
ports:
- "25000:5000"
volumes:
- ./app:/app
redis:
image: "redis:alpine"
综合例:
version: '3.8'
# 定义网络配置
networks:
ctf-frontend:
driver: bridge
driver_opts:
com.docker.network.bridge.name: ctf-front
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
gateway: 172.20.0.1
ctf-backend:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/24
# 数据卷配置
volumes:
mysql_data:
driver: local
driver_opts:
type: none
o: bind
device: ./data/mysql
redis_data:
driver: local
challenge_uploads:
driver: local
# 服务定义
services:
# Web前端服务
web-frontend:
build:
context: ./frontend
dockerfile: Dockerfile
args:
BUILD_ENV: production
image: ctf-frontend:latest
container_name: ctf-web-frontend
hostname: ctf-web
networks:
ctf-frontend:
ipv4_address: 172.20.0.10
ports:
- "80:80"
- "443:443"
expose:
- "8080"
volumes:
- ./frontend/app:/app:ro
- ./ssl:/etc/nginx/ssl:ro
- challenge_uploads:/uploads
environment:
- ENVIRONMENT=production
- DEBUG=false
- BACKEND_API_URL=http://api-backend:8000
- REDIS_URL=redis://redis-cache:6379
env_file:
- ./config/web.env
depends_on:
- api-backend
- redis-cache
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 256M
cpus: "0.5"
privileged: false
read_only: true
tmpfs:
- /tmp:size=100m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# API后端服务
api-backend:
build: ./backend
image: ctf-backend:latest
container_name: ctf-api-backend
hostname: ctf-api
networks:
ctf-frontend:
ctf-backend:
ipv4_address: 172.21.0.20
ports:
- "8000:8000"
volumes:
- ./backend/src:/app:ro
- ./backend/config:/config:ro
environment:
- DATABASE_URL=mysql://ctf_user:${MYSQL_PASSWORD}@mysql-db:3306/ctf
- REDIS_HOST=redis-cache
- REDIS_PORT=6379
- SECRET_KEY=${BACKEND_SECRET_KEY}
env_file:
- ./config/backend.env
depends_on:
mysql-db:
condition: service_healthy
redis-cache:
condition: service_started
restart: on-failure
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1"]
interval: 20s
timeout: 5s
retries: 5
stdin_open: false
tty: false
mem_limit: 1g
cpus: 2
labels:
- "ctf.service.type=api"
- "ctf.challenge.category=web"
# MySQL数据库服务
mysql-db:
image: mysql:8.0
container_name: ctf-mysql-db
hostname: ctf-mysql
networks:
ctf-backend:
ipv4_address: 172.21.0.30
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=ctf
- MYSQL_USER=ctf_user
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_INNODB_BUFFER_POOL_SIZE=256M
command:
- --default-authentication-plugin=mysql_native_password
- --innodb-buffer-pool-size=256M
- --max-connections=1000
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
security_opt:
- seccomp:unconfined
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
# Redis缓存服务
redis-cache:
image: redis:7-alpine
container_name: ctf-redis-cache
hostname: ctf-redis
networks:
ctf-backend:
ipv4_address: 172.21.0.40
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./redis/redis.conf:/etc/redis/redis.conf:ro
command: redis-server /etc/redis/redis.conf --requirepass ${REDIS_PASSWORD}
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD}
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 3s
retries: 5
# CTF题目容器 - Pwn挑战
pwn-challenge:
build:
context: ./challenges/pwn
dockerfile: Dockerfile.pwn
image: ctf-pwn-challenge:latest
container_name: ctf-pwn-challenge
networks:
ctf-backend:
ipv4_address: 172.21.0.50
ports:
- "9999:9999"
expose:
- "9999"
volumes:
- ./challenges/pwn/bin:/challenge:ro
environment:
- CHALLENGE_PORT=9999
- CHALLENGE_NAME=pwn_binary
- FLAG=${PWN_FLAG}
restart: unless-stopped
cap_add:
- SYS_PTRACE
security_opt:
- apparmor:unconfined
privileged: false
labels:
- "ctf.challenge.type=pwn"
- "ctf.difficulty=hard"
# CTF题目容器 - Web挑战
web-challenge:
build: ./challenges/web
image: ctf-web-challenge:latest
container_name: ctf-web-challenge
networks:
ctf-backend:
ipv4_address: 172.21.0.60
ports:
- "8081:80"
volumes:
- ./challenges/web/src:/var/www/html:ro
environment:
- FLAG=${WEB_FLAG}
- DB_HOST=mysql-db
depends_on:
- mysql-db
restart: unless-stopped
labels:
- "ctf.challenge.type=web"
- "ctf.difficulty=medium"
# 监控服务
monitor:
image: docker:latest
container_name: ctf-monitor
networks:
ctf-frontend:
ctf-backend:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./monitor/scripts:/scripts:ro
command: ["sh", "/scripts/monitor.sh"]
restart: unless-stopped
depends_on:
- web-frontend
- api-backend
实例
从 Ubuntu 构建 lamp 环境
# 使用 Ubuntu 官方镜像
FROM ubuntu:20.04
# 设置环境变量以避免交互式安装提示
ENV DEBIAN_FRONTEND=noninteractive
# 使用中科大源替换默认源
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
# 更新软件包列表并安装必要的软件
RUN apt-get update && apt-get install -y \
apache2 \
mysql-server \
php \
libapache2-mod-php \
php-mysql \
openssh-server \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 配置 SSH 服务
RUN mkdir /var/run/sshd
RUN echo 'root:root' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# 复制本地文件到容器中
COPY ./src /var/www/html
# 设置 Apache 和 MySQL 的权限
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html
# 暴露端口:80 (HTTP) 和 22 (SSH)
EXPOSE 80 22
# 启动服务
CMD service mysql start && \
service apache2 start && \
/usr/sbin/sshd -D
Docker上运行MongoDB
安装就不多说了
运行容器
docker run -itd --name mongo -p 27017:27017 mongo --auth
-p 27017:27017:映射容器服务的27017端口到宿主机的27017端口。外部可以直接通过宿主机ip:27017 访问到 mongo 的服务--auth:需要密码才能访问容器服务
添加用户(MongoDB > 6.0)
docker exec -it mongo mongosh admin
设置密码
db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},"readWriteAnyDatabase"]});
尝试使用上面创建的用户信息进行连接
db.auth('admin', '123456')

调试
php
vscode 配置
参考:https://depp.wang/2022/12/28/debug-docker-php-code-in-vscode/
- PHP 容器中安装 xdebug 依赖
# 查看 xdebug 是否已安装
php -m | grep xdebug
# 安装
pecl install xdebug
# 开启 xdebug
docker-php-ext-enable xdebug
# 重启容器
docker restart <your-php-container-name>
修改 xdebug 配置
容器内 /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
zend_extension=xdebug
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_port = 9003
xdebug.discover_client_host = true
xdebug.client_host = host.docker.internal
xdebug.remote_host = host.docker.internal
修改后重启容器
- vscode配置
.vscode/launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
// 容器映射本地
"/data/project/material/": "${workspaceFolder}" // /data/project/material/ 路径是自己 PHP Docker 的 WorkingDir
}
}
]
}
这里的mapping可以为多个文件之间的mapping,前面为容器中的地址后面为本地代码的地址
phpstorm 配置
参考 https://blog.51cto.com/u_16125110/14125181
- 同上,容器中安装 xdebug 依赖
php -i | grep xdebug查找并修改配置文件,修改后重启服务
zend_extension=xdebug
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_port = 9003
xdebug.discover_client_host = true
xdebug.client_host = host.docker.internal
xdebug.remote_host = host.docker.internal
xdebug.idekey = "phpstorm"
phpstorm 配置,在设置中配置一个来自 docker 的 cli 解释器,需要设置一下容器 web 路径到本地源码路径到映射

配置后会自动检测容器内的 xdebug然后进行调试即可

nodejs
参考:https://juejin.cn/s/debug%20node%20js%20docker%20container
在 Dockerfile 中使用
CMD ["node", "--inspect-brk", "app.js"]命令,以启用 Node.js 的调试模式,并在代码中插入debugger语句,以便在启动容器时自动停在调试器中断点处。使用
docker exec命令连接到正在运行的容器,并使用 Node.js 调试器手动附加到正在运行的 Node.js 进程,例如:docker exec -it <container-id> node --inspect-brk=0.0.0.0:9229 app.js,然后在 Chrome DevTools 中打开chrome://inspect页面,并连接到正在运行的调试器。使用
docker-compose配置文件启动容器,并在其中设置ports和command,以便在容器启动时自动启用 Node.js 调试器和调试模式,例如:version: '3' services: app: build: . ports: - "9229:9229" command: ["node", "--inspect-brk=0.0.0.0:9229", "app.js"]
python
参考:https://tttang.com/archive/1900/
Docker配置
方法一:vscode自动识别web框架
正常编写Docker文件即可,无需增加调试相关命令
方法二:通用远程调试
需要额外增加命令
- Dockerfile:
RUN pip install debugpy - docker-compose.yml:(ports之后)
entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client", "-m", "flask", "run", "-h", "0.0.0.0"],其中5678是vscode远程调试的端口
vscode调试配置
vscode 左侧调试栏->点击
新建调试文件/或c+s+p->Debug: Start Debugging选择
添加配置

主要以Django和flask框架为例(未加nodejs等),见0x131和0x132
方法一:vscode自动识别web框架
- 选择
python->Docker: Python - Django/flask - 会在工程目录下生成
.vscode目录,其中包含两个json文件: - launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"configurations": [
{
"name": "Docker: Python - Django",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"python": {
"pathMappings": [
{
"localRoot": "${workspaceFolder}/web",
"remoteRoot": "/usr/src"
}
],
"projectType": "django"
}
}
]
}
- tasks.json
{
"version": "2.0.0",
"tasks": [
{
"type": "docker-build",
"label": "docker-build",
"platform": "python",
"dockerBuild": {//自动生成,与docker文件保持一致
"tag": "",
"dockerfile": "${workspaceFolder}/Dockerfile",
"context": "${workspaceFolder}/[..]",
"pull": true
}
},
{
"type": "docker-run",
"label": "docker-run: debug",
"dependsOn":[
"docker-build"
],
"python": {
"args": [
"runserver",
"0.0.0.0:8000"
],
"file": "web/app.py" //程序主入口,需根据实际情况修改
}
}
]
}
注意:
- 这种情况vscode可能会修改Dockerfile,如果拿不准建议提前备份一下Docker配置
- 搭建github上的工程进行调试时,由于工程文件含requirements.txt,venv pip后实际调试失败,总报错:找不到依赖库
方法二:通用远程调试
- 选择
python->Python: 远程附加->输入调试端口,默认为5678 - 会在工程目录下生成
.vscode目录,其中包含1个json文件: - lanch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: 远程附加",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678 //远程调试端口
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"justMyCode": true
}
]
}
- docker-compose.yml的ports添加一行:
- "5678:5678"
vscode调试
在待调试文件代码处下断点
运行docker:docker-compose.yml右键->Compose Up(第一次)或Compose Restart->等待docker正常启动
开始调试:
方法一:运行和调试->自动有
Python: Remote Attach按钮,点击方法二:无
Python: Remote Attach时,让待调试文件处于活跃状态(即当前看见的代码是调试代码)->运行和调试->齿轮右侧省略号->开始调试
java
参考: https://blog.csdn.net/haduwi/article/details/126296308
先在本地对应jar包的文件夹下启动idea,把jar包添加为库
接下来编辑运行配置

指定端口,命令行实参选择对应版本,然后复制这行实参到我们的docker启动执行命令中(我这里是docker-entrypoint.sh)
把运行指令改为
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:6666 -jar /app/app.jar
/app/app.jar是我这里容器内的jar包路径
然后改docker-compose.yml,多设置一个端口
version: '3'
services:
web:
build: ../
environment:
# 仅为测试用flag
FLAG: "flag{a63b4d37-7681-4850-b6a7-0d7109febb19}"
ports:
# 设置了暴露端口
- 85:8080
- 6666:6666
restart: unless-stopped
接下来docker-compose up启动docker
IDEA这里还要在模块里把 BOOT-INF 添加到项目库依赖

然后就可以调试了