背景
我正在编写一个通过 USB 启动嵌入式 ARM 系统的工具。这个特定的 ARM 系统有一个引导加载程序,它可以通过模拟大容量存储设备并实现一些允许主机将信息写入内存的供应商 SCSI 操作码来通过 USB 加载系统。我的工具在嵌入式 ARM 系统所连接的主机上运行,它使用这些供应商命令将 zImage 或其他二进制文件发送到设备。
我使用 Linux 通用 SCSI 接口来发送命令。
在发送一些命令将值写入控制 RAM 控制器的寄存器后,我的程序打开一个文件,然后进入一个循环,在该循环中它一次从文件中读取 4096 个字节,然后将它们发送到设备。
对于需要发送的 SCSI 命令,我没有任何文档。我通过捕获和分析由供应商提供的等效纯 Windows 工具发送的 USB 流量来确定要使用的协议。这个协议有一些奇怪的方面,特别是它接受小端格式的地址和值,并且 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 做的事情导致了这种情况,但到目前为止我还没有发现任何东西。
欢迎任何见解!