2012-01-19 6 views
5

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)       = ? 
+3

Cosa * funziona *? Puoi scrivere 0 byte su quel file? Puoi scrivere 1 byte di quella stringa in quel file? – nickgrim

+0

@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. –

+2

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

risposta

9

MODIFICA: Continua a leggere per i dettagli cruenti, ma la risposta rapida è che il tuo client FTP sta corrompendo il tuo programma. Questa è una funzionalità intenzionale di FTP, che può essere disattivata digitando binary al prompt FTP prima di get whatever o put whatever. Se stai usando un client FTP grafico, dovrebbe avere una casella di controllo con lo stesso effetto. In alternativa, passare a scp, che non ha questa caratteristica sconveniente.


Prima di tutto, non v'è alcuna differenza nel codice assembly generato tra (uno dei) file oggetto di lavoro e il file oggetto rotto.

$ objdump -dr dc-good.o > dc-good.s 
$ objdump -dr dc-bad.o > dc-bad.s 
$ diff -u dc-good.s dc-bad.s 
--- dc-good.s 2012-01-21 08:20:05.318518596 -0800 
+++ dc-bad.s 2012-01-21 08:20:10.954566852 -0800 
@@ -1,5 +1,5 @@ 

-dc-good.o:  file format elf32-littlearm 
+dc-bad.o:  file format elf32-littlearm 


Disassembly of section .text: 

Infatti, ci sono solo due byte che differiscono tra la file oggetto cattivi buono e. (È frainteso quello che stavo chiedendo per con "test\r\n" contro "testX\n":. Volevo che le due stringhe di essere la lunghezza stesso , in modo che tutto ciò avrebbe lo stesso di offset nei file oggetto Fortunatamente, il compilatore imbottito la stringa più corta per la stessa lunghezza della stringa più lunga, quindi tutto ha compensato comunque la stessa )

$ hd dc-good.o > dc-good.x 
$ hd dc-bad.o > dc-bad.x 
$ diff -u1 dc-good.x dc-bad.x 
--- dc-good.x 2012-01-21 08:17:28.713174977 -0800 
+++ dc-bad.x 2012-01-21 08:17:39.129264489 -0800 
@@ -154,3 +154,3 @@ 
00000990 53 74 61 72 74 69 6e 67 20 70 72 6f 67 72 61 6d |Starting program| 
-000009a0 00 00 00 00 74 65 73 74 58 0a 00 00 2f 64 65 76 |....testX.../dev| 
+000009a0 00 00 00 00 74 65 73 74 58 0d 0a 00 2f 64 65 76 |....testX.../dev| 
000009b0 2f 74 74 79 53 30 00 00 66 64 20 3d 20 25 75 0a |/ttyS0..fd = %u.| 
@@ -223,3 +223,3 @@ 
00000de0 61 72 69 65 73 2f 64 61 74 61 63 6f 6c 6c 65 63 |aries/datacollec| 
-00000df0 74 6f 72 2d 62 61 64 2d 62 69 6e 61 72 79 2d 32 |tor-bad-binary-2| 
+00000df0 74 6f 72 2d 62 61 64 2d 62 69 6e 61 72 79 2d 31 |tor-bad-binary-1| 
00000e00 00 46 49 4c 45 00 5f 5f 73 74 61 74 65 00 5f 5f |.FILE.__state.__| 

la prima differenza è la differenza che dovrebbe esserci:. 74 65 73 74 58 0a 00 00 è la codifica corretta di "test\n" (con un byte di imbottitura), 74 65 73 74 58 0d 0a 00 è la codifica corretta di "test\r\n". L'altra differenza sembra essere il debug di informazioni : il nome della directory in cui sono stati compilati i programmi . Questo è innocuo.

I file oggetto sono come dovrebbero essere, quindi a questo punto è possibile regolare un errore nel compilatore o nell'assemblatore. Ora diamo un'occhiata agli eseguibili .

$ hd dc-good > dc-good.xe 
$ hd dc-bad > dc-bad.xe 
$ diff -u1 dc-good.xe dc-bad.xe 
--- dc-good.xe 2012-01-21 08:31:33.456437417 -0800 
+++ dc-bad.xe 2012-01-21 08:31:38.388480238 -0800 
@@ -120,3 +120,3 @@ 
00000770 f0 af 1b e9 53 74 61 72 74 69 6e 67 20 70 72 6f |....Starting pro| 
-00000780 67 72 61 6d 00 00 00 00 74 65 73 74 58 0a 00 00 |gram....testX...| 
+00000780 67 72 61 6d 00 00 00 00 74 65 73 74 58 0d 0a 00 |gram....testX...| 
00000790 2f 64 65 76 2f 74 74 79 53 30 00 00 66 64 20 3d |/dev/ttyS0..fd =| 
@@ -373,3 +373,3 @@ 
00001750 63 6f 6c 6c 65 63 74 6f 72 2d 62 61 64 2d 62 69 |collector-bad-bi| 
-00001760 6e 61 72 79 2d 32 00 46 49 4c 45 00 5f 5f 73 74 |nary-2.FILE.__st| 
+00001760 6e 61 72 79 2d 31 00 46 49 4c 45 00 5f 5f 73 74 |nary-1.FILE.__st| 
00001770 61 74 65 00 5f 5f 67 63 73 00 73 74 64 6f 75 74 |ate.__gcs.stdout| 

Le stesse due differenze, diversi offset all'interno dell'eseguibile. Questo è anche come dovrebbe essere. Possiamo anche escludere un bug nel linker (se si stava rovinando l'indirizzo della stringa, dovrebbe essere avvitarlo allo stesso modo in entrambi gli eseguibili e entrambi dovrebbero arrestarsi ).

A questo punto penso che stiamo osservando un errore nella libreria C o nel kernel . Per ridurlo ulteriormente, vorrei provare questo script test. Eseguilo come sh testz.sh sulla scheda ARM e inviaci l'output completo .

#! /bin/sh 

set -e 
cat >testz.c <<\EOF 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 

#define W(f, s) write(f, s, sizeof s - 1) 

int 
main(int ac, char **av) 
{ 
    int f; 
    if (ac != 2) return 2; 
    f = open(av[1], O_RDWR|O_NOCTTY|O_NONBLOCK); 
    if (f == -1) return 1; 

    W(f, "1\n"); 
    W(f, "12\n"); 
    W(f, "123\n"); 
    W(f, "1234\n"); 
    W(f, "12345\n"); 

    W(f, "1\r\n"); 
    W(f, "12\r\n"); 
    W(f, "123\r\n"); 
    W(f, "1234\r\n"); 

    close(f); 
    return 0; 
} 
EOF 

arm-linux-gcc -Wall -g testz.c -o testz 
set +e 
strace ./testz /dev/null 
echo ---- 
strace ./testz /dev/ttyS0 
echo ---- 
exit 0 

Ho guardato il binario danneggiato che hai fornito e ora so che cosa c'è che non va.

$ ls -l testz* 
-rwxr-x--- 1 zack zack 7528 Dec 31 1979 testz-bad 
-rwxr-x--- 1 zack zack 7532 Jan 21 16:35 testz-good 

Ignora il datestamp dispari; vedere come la versione -bad è più piccola di quattro byte rispetto alla versione -good? C'erano esattamente quattro \r caratteri nel codice sorgente. Diamo un'occhiata alle differenze nelle discariche esadecimali. Ho tirato fuori la parte interessante del diff e l'ho mescolata un po 'per rendere più facile vedere cosa sta succedendo.

00000620 00 00 00 00 31 32 33 34 0a 00 00 00 31 32 33 34 |....1234....1234| 

-00000630 35 0a 00 00 31 0d 0a 00 31 32 0d 0a 00 00 00 00 |5...1...12......| 
+00000630 35 0a 00 00 31 0a 00 31 32 0a 00 00 00 00 31 32 |5...1..12.....12| 

-00000640 31 32 33 0d 0a 00 00 00 31 32 33 34 0d 0a 00 00 |123.....1234....| 
+00000640 33 0a 00 00 00 31 32 33 34 0a 00 00 00 00 00 00 |3....1234.......| 

-00000650 00 00 00 00 68 84 00 00 1c 84 00 00 00 00 00 00 |....h...........| 
+00000650 68 84 00 00 1c 84 00 00 00 00 00 00 01 00 00 00 |h...............| 

trasferire il file sostituisce 0d 0a (cioè, \r\n) sequenze con 0a (solo \n). Questo fa sì che dopo questo punto nel file vengano spostati quattro byte da dove dovrebbe essere. Il codice è prima di questo punto, e così sono tutte le intestazioni ELF che il kernelguarda, motivo per cui non si ottiene

execve("./testz-bad", ["./testz-bad", "/dev/null"], [/* 36 vars */]) = -1 ENOEXEC (Exec format error) 

dallo script di test; invece, si ottiene un segfault all'interno del caricatore dinamico, poiché il segmento DYNAMIC (che indica al caricatore dinamico cosa fare) è dopo l'avvio del dislocamento.

$ readelf -d testz-bad 2> /dev/null 

Dynamic section at offset 0x660 contains 13 entries: 
    Tag  Type       Name/Value 
0x00000035 (<unknown>: 35)    0xc 
0x0000832c (<unknown>: 832c)   0xd 
0x00008604 (<unknown>: 8604)   0x19 
0x00010654 (<unknown>: 10654)   0x1b 
0x00000004 (HASH)      0x1a 
0x00010658 (<unknown>: 10658)   0x1c 
0x00000004 (HASH)      0x4 
0x00008108 (<unknown>: 8108)   0x5 
0x0000825c (<unknown>: 825c)   0x6 
0x0000815c (<unknown>: 815c)   0xa 
0x00000098 (<unknown>: 98)    0xb 
0x00000010 (SYMBOLIC)     0x15 
0x00000000 (NULL)      0x3 

Contrasto:

$ readelf -d testz-good 

Dynamic section at offset 0x660 contains 18 entries: 
    Tag  Type       Name/Value 
0x00000001 (NEEDED)      Shared library: [libc.so.0] 
0x0000000c (INIT)      0x832c 
0x0000000d (FINI)      0x8604 
0x00000019 (INIT_ARRAY)     0x10654 
0x0000001b (INIT_ARRAYSZ)    4 (bytes) 
0x0000001a (FINI_ARRAY)     0x10658 
0x0000001c (FINI_ARRAYSZ)    4 (bytes) 
0x00000004 (HASH)      0x8108 
0x00000005 (STRTAB)      0x825c 
0x00000006 (SYMTAB)      0x815c 
0x0000000a (STRSZ)      152 (bytes) 
0x0000000b (SYMENT)      16 (bytes) 
0x00000015 (DEBUG)      0x0 
0x00000003 (PLTGOT)      0x10718 
0x00000002 (PLTRELSZ)     56 (bytes) 
0x00000014 (PLTREL)      REL 
0x00000017 (JMPREL)      0x82f4 
0x00000000 (NULL)      0x0 

informazioni Il debugging è anche dopo lo spostamento, che è il motivo gdb non piace il programma.


Quindi, perché questa particolare corruzione? Non è un bug in qualcosa; è una funzionalità intenzionale del client FTP , che per impostazione predefinita trasferisce i file in "modalità testo", che significa (tra le altre cose) che converte terminazioni di linea in stile DOS (\r\n) in stile Unix (\n). Perché sarebbe quello che volevi se fosse il 1991 e stavi trasferendo file di testo dal tuo PC IBM al tuo file server istituzionale. Non è praticamente mai ciò che si vuole al giorno d'oggi, anche se si sono spostando i file di testo in giro. Fortunatamente, è possibile disattivarlo: basta digitare binary al prompt FTP prima dei comandi di trasferimento file. * Un * fortunatamente, per quanto ne so, non c'è modo di rendere quella chiavetta ; devi farlo ogni voltaRaccomando di passare a scp, che trasferisce sempre i file letteralmente ed è anche più facile da utilizzare dall'automazione di build.

+0

Grazie mille per il tuo dettagliato script di test! Questo è estremamente illuminante. Sebbene non disponga di strumenti di sviluppo installati nel sistema embedded, penso che il programma di test fornito nello script abbia aiutato a escludere problemi con la libreria standard o con il sistema Linux. Ho aggiornato la mia risposta sopra e ho anche fornito binari che dimostrano il danneggiamento che si verifica quando vengono trasferiti tramite FTP (https://docs.google.com/open?id=0B-xY4xORAoEDYmMxMmU4N2UtNDYzZi00NDI4LThjZjQtZDBiNmEyNGUwMzRh). –

+0

Se potessi dare un'occhiata a questi binari e aggiornare la tua risposta, accetterò la tua risposta. Grazie ancora per il tuo dettagliato e buon consiglio. –

+1

Ce l'ho adesso; si prega di vedere le modifiche. – zwol

1

Per prima cosa, il fatto che si veda solo l'errore del segmento NON è indicativo del fatto che il programma non è riuscito a funzionare affatto.Quello che succede è che l'output delle chiamate printf è bufferizzato dalla linea, e quando il programma segifica errori, non viene mai scritto.

Se si aggiunge fflush (stdout); dopo ogni printf, vedrai il tuo output prima del segfault.

Ora, nel programma originale, qual è il punto di fcntl (fd, F_SETFL, 0); chiamata? Cosa stai cercando di ottenere con esso? Stai cercando di disattivare la modalità non bloccante? Cosa succede se non si effettua quella chiamata?

Per quanto riguarda il tuo secondo test, vedo che stai usando perror, ma ancora una volta la mancanza di messaggi di errore non ti dice che il programma non è in esecuzione - ti dice solo che non hai ricevuto alcun errore messaggi, e ancora non stai scaricando lo stdout, quindi non vedrai mai la stampa da run_experiment.

Inoltre, nel secondo test si sta eseguendo un fdopen con la modalità di lettura, quindi si tenta di scrivere su quel puntatore FILE. Mentre certamente non dovrebbe andare in crash, certamente non dovrebbe funzionare.

Ora, al di fuori del programma, sei sicuro che la porta seriale funzioni correttamente? Prova a fare 'cat>/dev/ttyS0' e guarda cosa succede, solo per essere sicuro che non sia qualcosa di vistoso con l'hardware.

+0

'perror' scrive su' stderr', che * dovrebbe * essere bufferizzato dalla linea. Se questo fosse un normale ambiente C ospitato, 'stdout' sarebbe stato bufferato dalla linea tramite il rilevamento tty, ma questo potrebbe non essere vero per questo sistema embedded. – zwol

+0

@Zack: l'evidenza (la stampa da run_experiment non viene visualizzata) indica che la riga IS stdout è memorizzata in questo ambiente. il perror non dovrebbe essere, ma se avesse ricevuto un errore da uno di questi, non avrebbe colpito il segfault. –

+0

@ Michael: i tuoi commenti sono estremamente dettagliati e molto buoni. Ho provato ad aggiungere fflush (stdout) dopo ogni 'printf()', ma ancora non è stato mostrato nulla e si verifica il segfault. Ho provato a rimuovere la chiamata 'fcntl (fd, F_SETFL, 0)', ma il segfault si è verificato ancora. Come suggerisci, ho aggiornato il codice del programma nel secondo test, ma il segfault si verifica ancora all'inizio del programma. Ho anche provato a fare 'cat>/dev/ttyS0', e questo funziona bene. Tuttavia, come notato sopra, se rimuovo la sequenza di escape '\ r' dalla stringa, il programma gira! –