背景
我正在编写一个工具来通过 USB 启动嵌入式 ARM 系统。这个特定的 ARM 系统有一个引导加载程序,它可以通过模拟大容量存储设备并实现一些允许主机将信息写入内存的供应商 SCSI 操作码来通过 USB 加载系统。我的工具在嵌入式 ARM 系统所连接的主机上运行,它是使用这些供应商命令将 zImage 或其他二进制文件发送到设备。
我使用 Linux 通用 SCSI 接口(interface)来发送命令。
发送一些命令将值写入控制 RAM Controller 的寄存器后,我的程序打开一个文件,然后进入一个循环,在该循环中一次从文件中读取 4096 个字节,然后将它们发送到设备。
我没有任何需要发送的 SCSI 命令的文档。我通过捕获和分析由供应商提供的等效 Windows 专用工具发送的 USB 流量来确定要使用的协议(protocol)。该协议(protocol)有一些奇怪的方面,特别是它接受小端格式的地址和值,并且 SCSI 命令中的 32 位值不是字对齐的,但我认为这些与当前的问题没有任何关系。
问题
发送前 7 个缓冲区后,程序出现段错误。
出现段错误的部分如下:
int ak_usbboot_writefile(ak_usbboot_dev* dev, const char *filename, uint32_t addr) {
uint8_t dataBuff[DATABUFF_SIZE];
size_t len;
printf("STOREFILE: FILENAME=%s ADDR=%08x\n", filename, addr);
ak_usbboot_errno = AK_USBBOOT_OK;
FILE *f = fopen(filename, "rb");
if (f==NULL) {
ak_usbboot_errno = errno;
return errno;
}
/* Segfault occurs on the next line */
while ( (len = fread(dataBuff, 1, DATABUFF_SIZE, f)) > 0) {
printf("read len=%ld\n", len);
int r = ak_usbboot_storemem(dev, dataBuff, len, addr);
if (r!=AK_USBBOOT_OK) {
goto EXIT;
}
addr += len;
}
调用 fread 时发生段错误。回溯看起来像这样:
#0 __memcpy_sse2 () at ../sysdeps/x86_64/memcpy.S:272
#1 0x00007f92907b9233 in __GI__IO_file_xsgetn (fp=0x1f10030, data=<optimized out>, n=4096) at fileops.c:1427
#2 0x00007f92907ae9d8 in __GI__IO_fread (buf=<optimized out>, size=1, count=4096, fp=0x1f10030) at iofread.c:42
#3 0x0000000000401492 in ak_usbboot_writefile (dev=0x1f10010, filename=0x7fff078b0718 "/home/harmic/git/Lamobo-D1s/tool/burntool/zImage", addr=2174808064) at ak_usbboot.c:217
#4 0x0000000000400c4d in ak_boot (dev_name=0x7fff078b070f "/dev/sg2", file=0x7fff078b0718 "/home/harmic/git/Lamobo-D1s/tool/burntool/zImage") at main.c:86
#5 0x0000000000400d68 in cmd_boot (argc=2, argv=0x7fff078af538) at main.c:114
#6 0x0000000000400dfc in main (argc=4, argv=0x7fff078af528) at main.c:130
我看不出文件处理方式有任何问题,如果我注释掉对 ak_usbboot_storemem 的调用,则循环将毫无问题地完成。
ak_usbboot_storemem 看起来像这样:
int ak_usbboot_storemem(ak_usbboot_dev* dev, const void* buffer, uint32_t len, uint32_t addr) {
uint8_t cmdBuff[16] = {
0xf1, 0x3f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x68, 0, 0
};
printf("STORE: INBUFF=%p LEN=%08x ADDR=%08x\n", buffer, len, addr);
memcpy(&cmdBuff[5], &addr, 4);
memcpy(&cmdBuff[9], &len, 4);
return _sendCmd(dev, &cmdBuff, sizeof(cmdBuff), (void*)buffer, len, SG_DXFER_TO_DEV);
}
_sendCmd 看起来像这样:
int _sendCmd(ak_usbboot_dev* dev, const void* cmdBuff, int cmdLen, void* dataBuff, int dataLen, int sg_dir) {
fputs("CMD: ", stdout);
const uint8_t* p = (const uint8_t*)cmdBuff;
for (int i=0; i<cmdLen; i++) {
printf("%02x ", *p++);
}
fputs("\n", stdout);
sg_io_hdr_t io_hdr = {
.interface_id = 'S',
.dxfer_direction = sg_dir,
.cmd_len = cmdLen,
.mx_sb_len = sizeof(dev->sense_buffer),
.iovec_count = 0,
.dxfer_len = dataLen,
.dxferp = dataBuff,
.cmdp = (void*)cmdBuff,
.sbp = dev->sense_buffer,
.timeout = 10000,
.flags = 0,
.pack_id = 0,
};
if (ioctl(dev->fd, SG_IO, &io_hdr) < 0) {
ak_usbboot_errno = errno;
return ak_usbboot_errno;
}
if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
dev->sb_len = io_hdr.sb_len_wr;
dev->driver_status = io_hdr.driver_status;
dev->masked_status = io_hdr.masked_status;
dev->host_status = io_hdr.host_status;
ak_usbboot_errno = AK_USBBOOT_SCSIERR;
return AK_USBBOOT_SCSIERR;
} else {
dev->err = AK_USBBOOT_OK;
return AK_USBBOOT_OK;
}
}
我猜测我正在使用 SCSI Generic IOCTL 执行的操作导致了此问题,但到目前为止我还没有发现任何问题。
欢迎任何见解!
最佳答案
@Andrew Medico 的评论让我走上了正轨。我应该早点想到使用 valgrind。
Valgrind 报告了多个如下错误:
==28114== Invalid write of size 4
==28114== at 0x400FF5: _sendCmd (ak_usbboot.c:73)
==28114== by 0x4010D7: ak_usbboot_open (ak_usbboot.c:104)
==28114== by 0x400B7E: ak_boot (main.c:70)
==28114== by 0x400D67: cmd_boot (main.c:114)
==28114== by 0x400DFB: main (main.c:130)
==28114== Address 0x51f3074 is not stack'd, malloc'd or (recently) free'd
在 valgrind 下运行时,程序正常完成,按预期启动设备!
ak_usbboot.c:73 是这一行:
dev->err = AK_USBBOOT_OK;
这让我更仔细地了解开发人员的分配情况:
ak_usbboot_dev* dev = malloc(sizeof(dev));
哎呀。我为指向结构的指针分配了足够的空间,而不是结构本身。因此,写入结构会损坏堆。
当然应该是:
ak_usbboot_dev* dev = malloc(sizeof(*dev));
这个答案对其他人来说可能没有多大用处,除了作为如何追踪此类问题的提示 - valgrind 是天赐之物。
关于c - 通用 scsi ioctl 后 fread 期间出现段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28962444/