Segfault во время fread после универсального scsi ioctl

Задний план

Я пишу инструмент для загрузки встроенной системы ARM через USB. Эта конкретная система ARM имеет загрузчик, который может загружать систему через USB, эмулируя запоминающее устройство и реализуя коды операций SCSI некоторых поставщиков, которые позволяют хосту записывать информацию в память. Мой инструмент, который работает на хосте, к которому подключена встроенная система ARM, заключается в отправке zImage или другого двоичного файла на устройство с помощью этих команд поставщика.

Я использую общий интерфейс SCSI Linux для отправки команд.

После отправки нескольких команд для записи значений в регистры, управляющие контроллером ОЗУ, моя программа открывает файл, затем входит в цикл, в котором она считывает из файла по 4096 байт за раз, а затем отправляет их на устройство.

У меня нет документации по командам SCSI, которые необходимо отправить. Я определил протокол для использования, захватив и проанализировав USB-трафик, который отправляется с помощью эквивалентного инструмента только для Windows, который предоставляет поставщик. В этом протоколе есть несколько странных аспектов, в частности то, что он принимает адреса и значения в формате с прямым порядком байтов и что 32-битные значения в командах SCSI не выровнены по словам, однако я не думаю, что это имеет какое-либо отношение к рассматриваемой проблеме.

Проблема

После отправки первых 7 буферов программа дает сбой.

Раздел, который 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 возникает при вызове 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, вызывает это, но я пока ничего не смог обнаружить.

Любые идеи приветствуются!


person harmic    schedule 10.03.2015    source источник
comment
Это просто предположение, потому что я не знаком с этим, но должны ли &cmdBuff[5] и &cmdBuff[9] быть 32-битными, как &cmdBuff[4] и &cmdBuff[8]?   -  person Weather Vane    schedule 10.03.2015
comment
@WeatherVane, вы бы так подумали, но нет. Я определил формат командных пакетов, которые я должен отправить, прослушивая USB-трафик с помощью инструмента Windows поставщиков, и он не выровнен.   -  person harmic    schedule 10.03.2015
comment
Это должно работать в пользовательском пространстве? /просто спрашиваю   -  person joop    schedule 10.03.2015
comment
@joop да, он работает в пользовательском пространстве   -  person harmic    schedule 10.03.2015
comment
memcpy(&cmdBuff[5], &addr, 4); Использует ли протокол/контроллер SCSI формат с прямым порядком байтов в своих командах?   -  person joop    schedule 10.03.2015
comment
Кстати, всем командам SCSI, которые я проверил, сначала нужен MSB (:= по нижнему адресу). Я не могу себе представить, чтобы расширения поставщиков (0xf1) делали это по-другому.   -  person joop    schedule 10.03.2015
comment
@joop, как указано в предыдущем комментарии, протокол был определен путем захвата трафика из инструмента Windows поставщика. Я добавил немного больше деталей об этом. Команды, которые отправляются до segfault, принимаются устройством, и даже если бы это было не так, как это могло бы вызвать segfault на хосте?   -  person harmic    schedule 11.03.2015
comment
Тот факт, что у вас происходит сбой в fread, предполагает повреждение памяти. Попробуйте валгринд.   -  person nobody    schedule 11.03.2015
comment
Переполнение буфера могло быть вызвано перестановкой MSB‹--››LSB в поле длины. В любом случае: ошибка была вызвана ошибкой в ​​части кода, которая не была показана в OQ. Дело закрыто.   -  person joop    schedule 12.03.2015


Ответы (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));

Этот ответ, вероятно, не очень полезен для кого-либо еще, кроме как в качестве подсказки о том, как отслеживать такие проблемы - valgrind - это находка.

person harmic    schedule 11.03.2015