Segfault во время fread после универсального scsi ioctl
Фон
Я пишу инструмент для загрузки встроенной системы ARM через USB. Эта конкретная система ARM имеет загрузчик, который может загружать систему через USB, эмулируя запоминающее устройство большой емкости и реализуя коды операций SCSI некоторых поставщиков, которые позволяют хосту записывать информацию в память. Мой инструмент, который работает на хосте, к которому подключена встроенная система ARM, состоит в том, чтобы отправлять zImage или другой двоичный файл на устройство с помощью этих команд вендора.
Я использую универсальный интерфейс SCSI Linux для отправки команд.
После отправки нескольких команд для записи значений в регистры, которые управляют контроллером ОЗУ, моя программа открывает файл, затем входит в цикл, в котором она читает 4096 байт за раз из файла, а затем отправляет их на устройство.
У меня нет документации по командам SCSI, которые нужно отправить. Я определил протокол для использования, собирая и анализируя USB-трафик, который отправляется эквивалентным инструментом только для Windows, который предоставляет поставщик. У этого протокола есть некоторые странные аспекты, в частности то, что он принимает адреса и значения в формате с прямым порядком байтов и что 32-битные значения в командах SCSI не выровнены по словам, однако я не думаю, что они имеют какое-либо отношение к рассматриваемой проблеме.
Эта проблема
После отправки первых 7 буферов программа segfaults.
Раздел, который segfaults выглядит следующим образом:
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;
}
Segfault происходит, называя фред. Обратный след выглядит так:
#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, является причиной этого, но я пока не смог ничего найти.
Любые идеи приветствуются!
1 ответ
Комментарий @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;
Это побудило меня внимательнее присмотреться к месту расположения dev:
ak_usbboot_dev* dev = malloc(sizeof(dev));
К сожалению. Я выделял достаточно места для указателя на структуру, а не на саму структуру. В результате запись в структуру разрушала кучу.
Конечно, это должно было быть:
ak_usbboot_dev* dev = malloc(sizeof(*dev));
Этот ответ, вероятно, никому не нужен, кроме как для подсказки, как отследить такие проблемы - валгринд - находка.