Сегфаулт возникает из-за одной строки кода в файле C, и вся программа не запускается

Я создал программу на языке C для записи в последовательный порт (/dev/ttyS0) на встроенной ARM-системе. Ядро, работающее на встроенной ARM-системе, - это Linux версии 3.0.4, собранный с помощью того же кросс-компилятора, что и приведенный ниже.

Мой кросс-компилятор - arm-linux-gcc (Buildroot 2011.08) 4.3.6, работающий на хосте Ubuntu x86_64 (3.0.0-14-generic #23-Ubuntu SMP). Я использовал утилиту stty для настройки последовательного порта из командной строки.

Загадочно, но похоже, что программа отказывается запускаться на встроенной ARM-системе, если в ней присутствует одна строка кода. Если строка удалена, программа запускается.

Вот полный листинг кода, воспроизводящий проблему:

EDIT: Теперь я закрываю файл при ошибке, как было предложено в комментариях ниже.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <termios.h>

int test();
void run_experiment();

int main()
{
    run_experiment();
return 0;
}

void run_experiment()
{
    printf("Starting program\n");
    test();
} 

int test()
{
    int fd;
    int ret;

    fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
    printf("fd = %u\n", fd); 
    if (fd < 0)
    {
        close(fd);
        return 0;
    }

    fcntl(fd, F_SETFL, 0);

    printf("Now writing to serial port\n"); 

    //TODO:
    // segfault occurs due to line of code here
    // removing this line causes the program to run properly
    ret = write( fd, "test\r\n", sizeof("test\r\n") );

    if (ret < 0)
    {
        close(fd);
         return 0;
    }
close(fd);
return 1;
} 

Вывод этой программы на системе ARM выглядит следующим образом:

Segmentation fault

Однако, если я удалю строку, указанную выше, и перекомпилирую программу, проблема исчезает, и на выходе получается следующее:

Starting program
fd = 3
Now writing to serial port

Что здесь может идти не так, и как мне отладить проблему? Это может быть проблема с кодом, с кросс-компилятором компилятора или с версией ОС?

Я также пробовал различные комбинации O_WRONLY и O_RDWR без O_NOCTTY при открытии файла, но проблема все еще сохраняется.

Как предложил @wildplasser в комментариях ниже, я заменил тестовую функцию на следующий код, в значительной степени основанный на коде на другом сайте (http://www.warpspeed.com.au/cgi-bin/inf2html.cmd?.\html\book\Toolkt40\XPG4REF.INF+112).

Однако программа по-прежнему не запускается, и я снова получаю загадочное Segmentation Fault.

Вот код:

int test()
{
   int fh;
   FILE *fp;
   char *cp;

   if (-1 == (fh = open("/dev/ttyS0", O_RDWR))) 
   {
      perror("Unable to open");
      return EXIT_FAILURE;
   }
   if (NULL == (fp = fdopen(fh, "w"))) 
   {
      perror("fdopen failed");
      close(fh);
      return EXIT_FAILURE;
   }

   for (cp = "hello world\r\n"; *cp; cp++) 
   fputc( *cp, fp);

   fclose(fp);
   return 0;
}

Это очень загадочно, поскольку с помощью других программ, которые я написал, я могу использовать функцию write() аналогичным образом для записи в файлы sysfs без каких-либо проблем.

НО, если программа имеет точно такую же структуру, то я не могу писать в /dev/null.

НО я могу успешно писать в файл sysfs, используя точно такую же программу!

Если бы сбой произошел на определенной строке в функции, то я бы предположил, что вызов функции вызывает сбой. Однако полная программа не запускается!

UPDATE: Чтобы предоставить больше информации, вот информация о кросс-компиляторе, используемом для сборки на системе ARM:

$ arm-linux-gcc --v Использование встроенных спецификаций. Цель: arm-unknown-linux-uclibcgnueabi Сконфигурирован с: /media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011.08/output/toolchain/gcc-4.3.6/configure --prefix=/media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011. 08/output/host/usr --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-unknown-linux-uclibcgnueabi --enable-languages=c,c++ --with-sysroot=/media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011. 08/output/host/usr/arm-unknown-linux-uclibcgnueabi/sysroot --with-build-time-tools=/media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011. 08/output/host/usr/arm-unknown-linux-uclibcgnueabi/bin --disable-__cxa_atexit --enable-target-optspace --disable-libgomp --with-gnu-ld --disable-libssp --disable-multilib --enable-tls --enable-shared --with-gmp=/media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011. 08/output/host/usr --with-mpfr=/media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011. 08/output/host/usr --disable-nls --enable-threads --disable-decimal-float --with-float=soft --with-abi=aapcs-linux --with-arch=armv5te --with-tune=arm926ej-s --disable-largefile --with-pkgversion='Buildroot 2011.08' --with-bugurl=http://bugs.buildroot.net/ Потоковая модель: posix gcc версии 4.3.6 (Buildroot 2011.08)

Вот makefile, который я использую для компиляции моего кода:

CC=arm-linux-gcc
CFLAGS=-Wall
datacollector: datacollector.o 

clean:
    rm -f datacollector datacollector.o

UPDATE: Используя предложения по отладке, данные в комментариях и ответах ниже, Я обнаружил, что сегфаулт был вызван включением в строку управляющей последовательности \r. По какой-то странной причине компилятору не нравится \r, и он вызывает segfault без выполнения кода.

Если убрать \r, то код будет выполняться как ожидалось.

Таким образом, ошибочная строка кода должна быть следующей:

ret = write( fd, "test\n", sizeof("test\n") );

Итак, для протокола, полная тестовая программа, которая действительно выполняется, выглядит следующим образом (может кто-нибудь прокомментировать?):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <termios.h>

int test();
void run_experiment();

int main()
{
    run_experiment();

return 0;
}

void run_experiment()
{
    printf("Starting program\n");
    fflush(stdout);
    test(); 
}


int test()
{
    int fd;
    int ret;
    char *msg = "test\n";
    // NOTE: This does not work and will cause a segfault!
    // even if the fflush is called after each printf,
    // the program will still refuse to run
    //char *msg = "test\r\n";

    fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
    printf("fd = %u\n", fd); 
    fflush(stdout);

    if (fd < 0) 
    {
       close(fd);
       return 0;
    }

    fcntl(fd, F_SETFL, 0);

    printf("Now writing to serial port\n");
    fflush(stdout);

    ret = write( fd, msg, strlen(msg) );

    if (ret < 0)
    {
        close(fd);
        return 0;
    }

    close(fd);

return 1;
} 

EDIT: В качестве дополнения ко всему этому, лучше использовать:

ret = write( fd, msg, sizeof(msg) );

или лучше использовать:

ret = write( fd, msg, strlen(msg) );

Что лучше? Лучше использовать sizeof() или strlen()? Похоже, что некоторые данные в строке усекаются и не записываются в последовательный порт при использовании функции sizeof().

Как я понял из комментария Павла ниже, лучше использовать strlen(), если msg объявлен как char*.

Более того, похоже, что gcc не создает правильный двоичный файл, когда управляющая последовательность \r используется для записи на tty.

Ссылаясь на последнюю тестовую программу, приведенную в моем сообщении выше, следующая строка кода вызывает segfault без запуска программы:

char *msg = "test\r\n";

Как предложил Игорь в комментариях, я запустил отладчик gdb на бинарнике с ошибочной строкой кода. Мне пришлось скомпилировать программу с ключом -g. Отладчик gdb запущен на системе ARM, и все двоичные файлы собираются для архитектуры ARM на хосте с помощью одного и того же Makefile. Все двоичные файлы собираются с использованием кросс-компилятора arm-linux-gcc.

Вывод gdb (запущенного на ARM-системе) выглядит следующим образом:

GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-unknown-linux-uclibcgnueabi"...
"/programs/datacollector": not in executable format: File format not recognized
(gdb) run
Starting program:
No executable file specified.
Use the "file" or "exec-file" command.
(gdb) file datacollector
"/programs/datacollector": not in executable format: File format not recognized
(gdb)

Однако, если я изменю единственную строку кода на следующую, двоичный файл компилируется и запускается правильно. Обратите внимание, что \r отсутствует:

char *msg = "test\n";

Вот вывод gdb после изменения единственной строки кода:

GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-unknown-linux-uclibcgnueabi"...
(gdb) run
Starting program: /programs/datacollector
Starting program
fd = 4
Now writing to serial port
test
Program exited normally.
(gdb)

ОБНОВЛЕНИЕ:

Как предложил Зак в ответе ниже, я запустил тестовую программу на встроенной системе Linux-системе. Хотя Зак дает подробный сценарий для запуска на встроенной системе, я не смог не смог запустить сценарий из-за отсутствия средств разработки (компилятора и заголовков), установленных в корневой файловой системе. Вместо установки этих инструментов я просто скомпилировал хорошую тестовую программу, которую Зак предоставил в сценарии, и использовал утилиту strace. Утилита strace была запущена на встроенной системе.

Наконец, я думаю, что понимаю, что происходит.

Плохой двоичный файл был передан на встроенную систему по FTP, используя мост SPI-to-Ethernet (KSZ8851SNL). В ядре Linux есть драйвер для KSZ8851SNL.

Похоже, что либо драйвер ядра Linux, либо серверное ПО proftpd, работающее на встроенной системе, либо само оборудование (KSZ8851SNL) каким-то образом повреждало двоичный файл. На встроенной системе двоичный файл работает нормально.

Вот вывод strace на двоичный файл testz, переданный на встроенную систему Linux по последовательному каналу Ethernet:

Плохие двоичные тесты:

# strace ./testz /dev/null
execve("./testz", ["./testz", "/dev/null"], [/* 17 vars */]) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x40089000
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
+++ killed by SIGSEGV +++
Segmentation fault

# strace ./testz /dev/ttyS0
execve("./testz", ["./testz", "/dev/ttyS0"], [/* 17 vars */]) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x400ca000
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
+++ killed by SIGSEGV +++
Segmentation fault
#

Вот вывод strace на двоичный файл testz, переданный на SD-карту на встроенную систему Linux:

Хорошие двоичные тесты:

#  strace ./testz /dev/null
execve("./testz", ["./testz", "/dev/null"], [/* 17 vars */]) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x40058000
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=298016, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x400b8000
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\240\230\0\0004\0\0\0"..., 4096) = 4096
mmap2(NULL, 348160, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40147000
mmap2(0x40147000, 290576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x40147000
mmap2(0x40196000, 4832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x47) = 0x40196000
mmap2(0x40198000, 14160, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40198000
close(3)                                = 0
munmap(0x400b8000, 4096)                = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755, st_size=25296, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x400c4000
set_tls(0x400c4470, 0x400c4470, 0x4007b088, 0x400c4b18, 0x40) = 0
mprotect(0x40196000, 4096, PROT_READ)   = 0
mprotect(0x4007a000, 4096, PROT_READ)   = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B115200 opost isig icanon echo ...}) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B115200 opost isig icanon echo ...}) = 0
open("/dev/null", O_RDWR|O_NOCTTY|O_NONBLOCK) = 3
write(3, "1\n", 2)                      = 2
write(3, "12\n", 3)                     = 3
write(3, "123\n", 4)                    = 4
write(3, "1234\n", 5)                   = 5
write(3, "12345\n", 6)                  = 6
write(3, "1\r\n", 3)                    = 3
write(3, "12\r\n", 4)                   = 4
write(3, "123\r\n", 5)                  = 5
write(3, "1234\r\n", 6)                 = 6
close(3)                                = 0
exit_group(0)                           = ?


#  strace ./testz /dev/ttyS0
execve("./testz", ["./testz", "/dev/ttyS0"], [/* 17 vars */]) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x400ed000
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=298016, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x40176000
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\240\230\0\0004\0\0\0"..., 4096) = 4096
mmap2(NULL, 348160, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40238000
mmap2(0x40238000, 290576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x40238000
mmap2(0x40287000, 4832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x47) = 0x40287000
mmap2(0x40289000, 14160, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40289000
close(3)                                = 0
munmap(0x40176000, 4096)                = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755, st_size=25296, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x400d1000
set_tls(0x400d1470, 0x400d1470, 0x40084088, 0x400d1b18, 0x40) = 0
mprotect(0x40287000, 4096, PROT_READ)   = 0
mprotect(0x40083000, 4096, PROT_READ)   = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B115200 opost isig icanon echo ...}) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B115200 opost isig icanon echo ...}) = 0
open("/dev/ttyS0", O_RDWR|O_NOCTTY|O_NONBLOCK) = 3
write(3, "1\n", 21
)                      = 2
write(3, "12\n", 312
)                     = 3
write(3, "123\n", 4123
)                    = 4
write(3, "1234\n", 51234
)                   = 5
write(3, "12345\n", 612345
)                  = 6
write(3, "1\r\n", 31
)                    = 3
write(3, "12\r\n", 412
)                   = 4
write(3, "123\r\n", 5123
)                  = 5
write(3, "1234\r\n", 61234
)                 = 6
close(3)                                = 0
exit_group(0)                           = ?
5
задан 16 revs, 2 users 100% 22 January 2012 в 04:03
поделиться