execve_hook/TEST_DEBUG.md

153 lines
5.1 KiB
Markdown

# DEBUG 模式测试说明
## 问题描述
之前遇到的 "connection reset by peer" 错误的根本原因是:
1. **TCP 流式传输特性**: 数据可能分多次到达,不能假设一次 `read()` 就能读到完整数据
2. **部分读取问题**: 原代码只调用一次 `read()`,导致读取不完整的消息头或载荷
3. **错误表现**: 客户端读取消息头时只收到 12 字节(期望 16 字节),导致解析失败并关闭连接
## 修复方案
### C 代码修复 (`socket_protocol.c`)
1. **添加 `read_full()` 函数**: 循环读取直到获取完整的指定字节数
```c
static ssize_t read_full(int sock, void* buf, size_t count) {
// 循环读取,处理 EINTR 和部分读取
// 确保读取到 count 字节或连接关闭
}
```
2. **添加 `write_full()` 函数**: 循环写入直到发送完整的指定字节数
```c
static ssize_t write_full(int sock, const void* buf, size_t count) {
// 循环写入,处理 EINTR 和部分写入
// 确保写入 count 字节
}
```
3. **修改 `read_message()`**: 使用 `read_full()` 读取消息头和载荷
4. **修改 `write_message()`**: 使用 `write_full()` 发送消息头和载荷
### Go 代码修复 (`protocol.go`)
1. **使用 `io.ReadFull()`**: 确保完整读取消息头和载荷
```go
// 读取消息头
headerBuf := make([]byte, 16)
if _, err := io.ReadFull(conn, headerBuf); err != nil {
return 0, nil, err
}
// 读取载荷
if _, err := io.ReadFull(conn, payload); err != nil {
return 0, nil, err
}
```
2. **完整写入**: 构建完整缓冲区后循环写入
```go
written := 0
for written < len(buf) {
n, err := conn.Write(buf[written:])
if err != nil {
return err
}
written += n
}
```
## DEBUG 日志系统
### C 代码 DEBUG 功能
1. **`debug.h` `debug.c`**:
- `DEBUG_LOG(fmt, ...)`: 带时间戳文件位置的日志
- `DEBUG_HEX(prefix, data, len)`: 十六进制数据转储
- 只在 `DEBUG=1` 编译时启用
2. **关键日志点**:
- 消息发送/接收的开始和完成
- 消息头和载荷的十六进制转储(前64字节)
- 读写进度(部分读取时)
- 终端模式切换
- 连接生命周期事件
### 编译和测试
```bash
# 1. 编译 DEBUG 模式的 C 客户端
cd /home/qcqcqc/workspace/Projects/bash_smart/execve_hook
make clean
make DEBUG=1 test_socket_client
# 2. 在一个终端启动 Go 服务端
cd /home/qcqcqc/workspace/Projects/bash_smart/go_service
sudo GOPROXY=https://goproxy.cn,direct go run ./cmd/tests/test_socket_terminal/progress-animated/main.go
# 3. 在另一个终端运行 DEBUG 客户端
cd /home/qcqcqc/workspace/Projects/bash_smart/execve_hook
./build/test_socket_client 2> debug.log
# 4. 查看 DEBUG 日志
tail -f debug.log
```
### DEBUG 日志示例
```
[DEBUG 16:54:33.123] [src/client.c:320:seeking_solutions] 开始 seeking_solutions: filename=/usr/bin/ls
[DEBUG 16:54:33.124] [src/client.c:355:seeking_solutions] 尝试连接到 /var/run/bash-smart.sock
[DEBUG 16:54:33.125] [src/client.c:365:seeking_solutions] 连接成功
[DEBUG 16:54:33.126] [src/socket_protocol.c:35:write_message] 写入消息: type=1, payload_len=1024
[DEBUG HEX] 消息头 (16 bytes):
54 4d 53 42 01 00 00 00 00 04 00 00 00 00 00 00
[DEBUG 16:54:33.127] [src/socket_protocol.c:65:write_full] 进度 1024/1024 字节
[DEBUG 16:54:33.128] [src/socket_protocol.c:76:read_message] 开始读取消息...
[DEBUG 16:54:33.129] [src/socket_protocol.c:96:read_full] 进度 16/16 字节
[DEBUG HEX] 收到消息头 (16 bytes):
54 4d 53 42 03 00 00 00 6c 03 00 00 00 00 00 00
[DEBUG 16:54:33.130] [src/socket_protocol.c:100:read_message] 消息类型=3, 载荷长度=876
```
## 验证修复
修复后应该看到:
1. 所有消息完整读取,没有 "Failed to read message header" 错误
2. 连接正常完成,TUI 显示完整流程
3. 终端正确恢复,光标在新行开始
4. DEBUG 日志显示所有读写操作成功完成
## 技术要点
### 为什么需要完整读写?
1. **TCP 流特性**: TCP 是字节流协议,不保证消息边界
2. **内核缓冲区**: 数据可能在内核缓冲区中分批到达
3. **网络延迟**: 跨网络通信时更容易出现部分读取
4. **信号中断**: `EINTR` 会导致系统调用提前返回
### `read_full()` vs `read()`
| 函数 | 行为 | 适用场景 |
|------|------|---------|
| `read()` | 返回当前可用的数据,可能少于请求的字节数 | 不确定数据长度的场景 |
| `read_full()` | 循环读取直到获取完整的请求字节数或错误 | 已知数据长度的协议(如我们的消息协议) |
### Go 的 `io.ReadFull()`
Go 标准库提供了 `io.ReadFull()`,其行为等同于我们实现的 `read_full()`:
- 返回 `io.ErrUnexpectedEOF` 如果只读取了部分数据就遇到 EOF
- 返回 `io.EOF` 如果一个字节都没读到就遇到 EOF
- 只在成功读取完整字节数时返回 `nil` 错误
## 后续改进
1. 添加读写超时机制
2. 添加消息大小限制(防止内存攻击)
3. 实现消息队列缓冲
4. 添加压缩支持(大载荷)
5. 添加加密支持(敏感数据)