Ho creato un programma C per scrivere su una porta seriale (/ dev/ttyS0) su un sistema ARM incorporato. Il kernel in esecuzione sul sistema ARM incorporato è Linux versione 3.0.4, costruito con lo stesso cross-compilatore di quello elencato di seguito.Segfault si verifica a causa di una riga di codice nel file C e l'intero programma non viene eseguito
Il mio cross-compilatore è arm-linux-gcc (Buildroot 2011.08) 4.3.6, in esecuzione su un host Ubuntu x86_64 (3.0.0-14-generico # 23-Ubuntu SMP). Ho usato l'utilità stty per impostare la porta seriale dalla riga di comando.
Misteriosamente, sembra che il programma si rifiuti di eseguire sul sistema ARM incorporato se è presente una singola riga di codice. Se la linea viene rimossa, il programma verrà eseguito.
Ecco un codice lista completa replicare il problema:
EDIT: ora chiudere il file in caso di errore, come suggerito nei commenti qui sotto.
#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;
}
L'uscita di questo programma sul sistema ARM è il seguente:
Segmentation fault
Tuttavia, se rimuovere la linea di cui sopra e ricompilare il programma, il problema scompare, e l'uscita è il seguente:
Starting program
fd = 3
Now writing to serial port
Che cosa potrebbe andare storto qui, e come faccio a risolvere il problema? Questo sarebbe un problema con il codice, con il compilatore cross-compilatore o con una versione del sistema operativo?
Ho anche provato varie combinazioni di O_WRONLY e O_RDWR senza O_NOCTTY all'apertura del file, ma il problema persiste ancora.
Come suggerito da @wildplasser nei commenti di seguito, ho sostituito la funzione di test con il seguente codice, fortemente basato sul codice in un altro sito (http://www.warpspeed.com.au/cgi-bin/ inf2html.cmd? .. \ html \ libro \ Toolkt40 \ XPG4REF.INF + 112).
Tuttavia, il programma continua a non funzionare e ricevo di nuovo il misterioso Segmentation Fault
.
Ecco il codice:
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;
}
Questo è molto misterioso, dal momento che con altri programmi che ho scritto, posso utilizzare la funzione write()
in un modo simile a scrivere su file sysfs, senza alcun problema.
TUTTAVIA, se il programma si trova esattamente nella stessa struttura, non è possibile scrivere su/dev/null.
MA Posso scrivere correttamente su un file sysfs utilizzando esattamente lo stesso programma!
Se il segfault si è verificato su una particolare riga della funzione, quindi suppongo che la chiamata alla funzione causerebbe il segfault. Tuttavia, il programma completo non funziona!
AGGIORNAMENTO: Per fornire maggiori informazioni, qui sono le informazioni cross-compilatore utilizzato per costruire il sistema di ARM:
$ arm-linux-gcc --V Uso specifiche incorporate. Destinazione: arm-unknown-linux-uclibcgnueabi Configurato con: /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/ modello Discussione: posix versione di gcc 4.3.6 (Buildroot 2011.08)
Ecco l'makefile che sto usando per compilare il mio codice:
CC=arm-linux-gcc
CFLAGS=-Wall
datacollector: datacollector.o
clean:
rm -f datacollector datacollector.o
UPDATE: Usando il debug suggerimenti forniti nei commenti e risposte di seguito, ho trovato che il segfault è stato causato includendo il \r
sequenza di escape nella stringa. Per qualche strana ragione, il compilatore non ama la sequenza di escape \r
e causerà un segfault senza eseguire il codice.
Se la sequenza di escape \r
viene rimossa, il codice viene eseguito come previsto.
Pertanto, la riga di codice dovrebbe essere la seguente:
ret = write (fd, "test \ n", sizeof ("test \ n"));
Così, per la cronaca, un programma di test completo che viene eseguito in realtà è la seguente (qualcuno potrebbe commentare?):
#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: Per inciso a tutto questo, è meglio usare:
ret = write(fd, msg, sizeof(msg));
o è meglio usare:
ret = write(fd, msg, strlen(msg));
che è meglio? È meglio usare sizeof() o strlen()? Sembra che alcuni dei dati nella stringa siano troncati e non scritti sulla porta seriale usando la funzione sizeof().
quanto ho capito dal commento di Pavel di seguito, è meglio usare strlen()
se msg
viene dichiarato come char*
.
Inoltre, gcc non sta creando un binario corretto quando viene utilizzata la sequenza di escape \r
per scrivere su un tty.
Riferendosi all'ultimo programma di test dato nel mio post precedente, la seguente riga di codice causa un segfault senza il programma in esecuzione:
char *msg = "test\r\n";
Come suggerito da Igor nei commenti, ho eseguito il debugger gdb sul binario con la riga di codice offendente. Ho dovuto compilare il programma con lo switch -g
. Il debugger gdb viene eseguito in modo nativo sul sistema ARM e tutti i file binari vengono creati per l'architettura ARM sull'host utilizzando lo stesso Makefile. Tutti i file binari vengono creati utilizzando il cross-compiler arm-linux-gcc.
L'uscita del gdb (eseguito in nativo sul sistema ARM) è il seguente:
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)
Tuttavia, se cambio la singola riga di codice alla seguente, le compilazioni binari e funziona correttamente. Si noti che la sequenza di escape \r
manca:
char *msg = "test\n";
Ecco l'output di gdb dopo aver cambiato la singola riga di codice:
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:
Come suggerito da Zack in una risposta di seguito, ho eseguito un programma di test sul sistema Linux integrato . Sebbene Zack fornisca uno script dettagliato da eseguire sul sistema embedded, io ero incapace di eseguire lo script a causa della mancanza di strumenti di sviluppo (compilatore e intestazioni) installati nel file system radice. Invece di installare questi strumenti, ho semplicemente compilato il bel programma di test che Zack ha fornito nello script e utilizzato l'utilità strace. L'utilità strace è stata eseguita sul sistema incorporato.
Finalmente, penso di capire cosa sta succedendo.
Il file binario errato è stato trasferito al sistema incorporato tramite FTP, utilizzando un bridge da SPI a Ethernet (KSZ8851SNL). C'è un driver per KSZ8851SNL nel kernel Linux.
Sembra che il driver del kernel di Linux, il software del server ProFTPD in esecuzione sul sistema embedded, o l'hardware vero e proprio (KSZ8851SNL) era in qualche modo danneggiare il binario. Il binario funziona bene sul sistema incorporato.
Ecco l'output di strace sul binario testz trasferito al sistema Linux integrato sopra il collegamento seriale Ethernet:
Bad test binari:
# 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
#
Ecco l'uscita del strace sul testz binario trasferito su scheda SD al sistema Linux embedded:
Buone prove di binari:
# 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) = ?
Cosa * funziona *? Puoi scrivere 0 byte su quel file? Puoi scrivere 1 byte di quella stringa in quel file? – nickgrim
@nickgrim: Grazie mille per questo commento. Stranamente, sembra che sia una riga di codice che fa sì che l'intero programma diventi segfault. Sembra che l'esecuzione del programma non raggiunga la linea, e non posso scrivere 0 byte né 1 byte della stringa sul file fd. –
Forse la scrittura non è consentita per i fd non ricercabili? Prova un loop su fputc(): 'for (cp =" ciao mondo \ r \ n "; * cp; cp ++) fputc (* cp, fp);', oops: che ha bisogno di un fdopen, ovviamente ... – wildplasser