Я создал программу на C для записи в последовательный порт (/ dev / ttyS0) во встроенной системе ARM. Ядром, работающим во встроенной системе ARM, является версия 3.0.4 Linux, построенная с использованием того же кросс-компилятора, что и приведенный ниже.
Мой кросс-компилятор - arm-linux-gcc (Buildroot 2011.08) 4.3.6, работающий на хосте Ubuntu x86_64 (3.0.0-14-generic # 23-Ubuntu SMP). Я использовал утилиту stty для настройки последовательного порта из командной строки.
Таинственным образом кажется, что программа откажется работать во встроенной системе ARM, если присутствует одна строка кода. Если строка будет удалена, программа запустится.
Вот полный список кода, повторяющий проблему:
РЕДАКТИРОВАТЬ: теперь я закрываю файл по ошибке, как это предлагается в комментариях ниже.
#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, используя точно такую же программу!
Если segfault произошел в определенной строке функции, то я бы предположил, что вызов функции будет вызывать segfault. Однако полная программа не запускается!
ОБНОВЛЕНИЕ: Для получения дополнительной информации вот информация о кросс-компиляторе, используемая для сборки в системе 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 / система устройств / buildroot / buildroot-2011.08 / output / host / usr / arm-unknown-linux-uclibcgnueabi / sysroot --with-build-time -tools = / media / RESEARCH / SAS2-версия2 / система устройств / 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 / устройство-система / buildroot / buildroot-2011.08 / output / host / usr --with-mpfr = / media / RESEARCH / SAS2-version2 / система устройств / buildroot / buildroot-2011.08 / output / host / usr --disable-n ls --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)
Вот make-файл, который я использую для компиляции моего кода:
CC=arm-linux-gcc
CFLAGS=-Wall
datacollector: datacollector.o
clean:
rm -f datacollector datacollector.o
ОБНОВЛЕНИЕ: Используя предложения по отладке, приведенные в комментариях и ответах ниже, я обнаружил, что ошибка вызвана включением escape-последовательности \r
в строку.По какой-то странной причине компилятору не нравится escape-последовательность \r
, и она вызовет segfault без запуска кода.
Если escape-последовательность \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;
}
РЕДАКТИРОВАТЬ: В дополнение ко всему этому, лучше использовать:
ret = write( fd, msg, sizeof(msg) );
или лучше использовать:
ret = write( fd, msg, strlen(msg) );
Что лучше?Лучше использовать sizeof () или strlen ()?Похоже, что некоторые данные в строке усекаются и не записываются в последовательный порт с использованием функции sizeof ().
Как я понимаю из комментария Павла ниже, лучше использовать strlen()
если msg
объявлено как char*
.
Более того, похоже, что gcc не создает правильный двоичный файл, когда escape-последовательность \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)
Однако, если я изменю одну строку кода на следующую, двоичный файл компилируется и работает правильно.Обратите внимание, что escape-последовательность \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)
UPDATE:
Как предложено Заком в ответе ниже, я запустил тестовую программу для встроенной системы Linux.Хотя Зак предоставляет подробный сценарий для запуска во встроенной системе, я не смог запустить сценарий из-за отсутствия инструментов разработки (компилятор и заголовки), установленных в корневой файловой системе.Вместо установки этих инструментов я просто скомпилировал хорошую тестовую программу, которую Зак предоставил в скрипте, и использовал утилиту strace.Утилита strace была запущена во встроенной системе.
Наконец, я думаю, что понимаю, что происходит.
Плохой двоичный файл был передан во встроенную систему через FTP с использованием SPI-мост 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) = ?