2012-01-19 60 views
5

我已經創建了一個C程序來寫入嵌入式ARM系統上的串行端口(/ dev/ttyS0)。在嵌入式ARM系統上運行的內核是Linux版本3.0.4,使用與下面列出的相同的交叉編譯器構建。由於C文件中有一行代碼而導致段錯誤,整個程序不運行

我的交叉編譯器是在Ubuntu x86_64主機(3.0.0-14-generic#23-Ubuntu SMP)上運行的arm-linux-gcc(Buildroot 2011.08)4.3.6。我已經使用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 

這裏可能會出現什麼問題,以及如何調試問題?這是代碼,交叉編譯器編譯器還是OS版本的問題?

打開文件時,我也嘗試過O_WRONLY和O_RDWR沒有O_NOCTTY的各種組合,但問題仍然存在。

正如@wildplasser在下面的評論中所建議的那樣,我用下面的代碼替代了測試函數,這些代碼很大程度上取決於另一個站點上的代碼(http://www.warpspeed.com.au/cgi-bin/ inf2html.cmd?.. \ HTML \書\ 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系統上的交叉編譯器的信息:

$手臂-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)

這裏是我使用的編譯我的代碼生成文件:

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

clean: 
    rm -f datacollector datacollector.o 

UPDATE:使用調試在下面的評論和答案中給出的建議,我發現segfault是由包含引起的字符串中的轉義序列。出於某種奇怪的原因,編譯器不喜歡\r轉義序列,並且會在不運行代碼的情況下導致段錯誤。

如果\r轉義序列被刪除,則代碼按預期運行。

因此,問題的代碼行應爲以下:

RET =寫入(FD, 「測試\ n」 個,的sizeof( 「測試\ 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()函數寫入串行端口。

據我所知Pavel的評論如下,如果msg被聲明爲char*,最好使用strlen()

此外,當使用轉義序列\r寫入tty時,gcc似乎沒有創建適當的二進制文件。

參考上面我的帖子中給出的最後一個測試程序,下面的代碼行導致段錯誤沒有程序運行:

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

正如意見建議由Igor,我已經運行gdb調試器在有問題的代碼行的二進制文件上。我不得不用-g開關編譯程序。 gdb調試器本身在ARM系統上運行,並且所有的二進制文件都是在主機上使用相同的Makefile爲ARM架構構建的。所有的二進制文件都是使用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) 

UPDATE:

正如在回答中建議由扎克下面,我現在已經在嵌入式Linux系統上運行了一個測試程序。雖然Zack提供了一個在嵌入式系統上運行的詳細腳本,但由於缺少安裝在根文件系統中的開發工具(編譯器和頭文件),因此我無法運行腳本 。 代替安裝這些工具,我簡單地編譯了Zack在腳本中提供的漂亮測試程序,並使用了strace實用程序 。 Strace實用程序在嵌入式系統上運行。

最後,我想我明白髮生了什麼。

使用SPI到以太網橋(KSZ8851SNL),通過FTP將錯誤的二進制文件傳輸到嵌入式系統。 在Linux內核中有一個KSZ8851SNL的驅動程序。

看來,無論是Linux內核驅動程序,嵌入式系統上運行proftpd的服務器軟件,或實際硬件本身(KSZ8851SNL) 在某種程度上破壞二元。二進制在嵌入式系統上運行良好。

下面是使用strace對通過以太網串行鏈路傳送到嵌入式Linux系統的testz二進制輸出:

壞二進制測試:

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

What * does * work?你能寫0字節到那個FD?你能寫一個字節的那個字符串到那個fd嗎? – nickgrim

+0

@nickgrim:非常感謝您的評論。奇怪的是,這似乎是導致整個程序出現段錯誤的一行代碼。看起來程序執行沒有到達該行,並且我不能將字符串的0個字節或1個字節寫入fd。 –

+2

也許寫不允許非尋找fd的?嘗試圍繞fputc()循環:'for(cp =「hello world \ r \ n」; * cp; cp ++)fputc(* cp,fp);',oops:當然需要fdopen ... – wildplasser

回答

9

編輯:閱讀血淋淋的細節,但快速回答是,你的FTP客戶端正在破壞你的程序。這是FTP的一項有意的功能,可以通過在get whateverput whatever之前的FTP提示符處輸入binary來關閉該功能。如果你使用的是圖形化的FTP客戶端,它應該有一個複選框,具有相同的效果。或切換到scp,這沒有這個不便的功能。


首先,有在之間生成的彙編代碼 沒有差異(中的一個)加工對象物的文件和破碎的對象文件。

$ 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: 

事實上,只有兩個字節的好和壞 目標文件之間的差異。 (你誤會了我的要求與 "test\r\n""testX\n":我想這兩個字符串是 相同長度,讓一切都將具有相同的 目標文件偏移幸運的是,你的編譯器補齊短串到 相同的長度較長的字符串,所以一切都相同 反正偏移)

$ 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.__| 

第一個區別是,應該是有差異:74 65 73 74 58 0a 00 00"test\n"正確的編碼(一個字節 的填充),74 65 73 74 58 0d 0a 00"test\r\n"的正確編碼。另一個區別似乎是調試 信息:編譯 程序的目錄的名稱。這是無害的。

目標文件應該是這樣,所以在這一點上,我們可以將 排除在編譯器或彙編程序的錯誤之外。現在我們來看看 可執行文件。

$ 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| 

相同的兩個差異,可執行文件中的不同偏移量。這 也是它應該的。我們可以排除鏈接器中的一個錯誤 (如果它搞砸了字符串的地址,它必須是 在兩個可執行文件中以相同的方式搞砸了它們,並且它們都應該崩潰)。

在這一點上,我想我們正在看你的C庫或 內核的錯誤。爲了進一步把它放下,我想讓你嘗試這個測試 腳本。在ARM板上將其作爲sh testz.sh運行,併發送給我們 完整輸出。

#! /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 

我看你提供的二進制文件損壞,現在我知道什麼是錯。

$ 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 

忽略奇數datestamp;請參閱-bad版本如何比-good版本小四個字節?源代碼中只有四個\r字符。我們來看看十六進制轉儲的差異。我已經拿出了差異的有趣的一點,並稍微攪動它,以便更容易地看到發生了什麼。

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

文件傳送與0a(只是\n)替換0d 0a(即,\r\n)序列。這導致文件中的這一點之後的所有內容都將從應該出現的位置移動四個字節。該代碼是這點之前,等等都是ELF頭的內核看,這就是爲什麼你沒有得到

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

;相反,您會在動態加載器中發現段錯誤,因爲DYNAMIC段(它告訴動態加載器該做什麼)在位移開始後爲

$ 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 

對比度:

$ 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 

的調試信息也是位移,這就是爲什麼GDB不喜歡的節目後。


那麼爲什麼這個非常特殊的腐敗呢?這不是什麼問題;這是您的FTP 客戶端的一個有意功能,它默認以「文本模式」傳輸文件,這意味着(其中包括)它將DOS樣式的行結尾(\r\n)轉換爲Unix樣式(\n)。因爲如果這是1991年,那麼這就是你想要的,並且你將文本文件從IBM PC轉移到你的機構文件服務器。現在基本上從來沒有這樣想過,即使你都是移動文本文件。幸運的是,您可以關閉它:只需在文件傳輸命令之前的FTP提示符處輸入binary即可。 *幸運的是,據我所知,沒有辦法使;你必須每次都這樣做。我建議切換到scp,它總是逐字傳輸文件,並且從構建自動化中操作也更容易。

+0

非常感謝您的詳細測試腳本!這非常有啓發性。儘管我沒有在嵌入式系統上安裝開發工具,但我認爲腳本中給出的測試程序有助於排除標準庫或Linux系統的問題。我已經更新了上面的答案,並且還提供了二進制文件,演示通過FTP傳輸時發生的損壞情況(https://docs.google.com/open?id=0B-xY4xORAoEDYmMxMmU4N2UtNDYzZi00NDI4LThjZjQtZDBiNmEyNGUwMzRh)。 –

+0

如果你可以看看這些二進制文件並更新你的答案,我會接受你的答案。再次感謝您的詳細和良好的建議。 –

+1

我現在擁有它;請參閱修改。 – zwol

1

首先要做的事 - 你只能看到seg故障的事實並不表示程序完全沒有運行。會發生什麼情況是printf調用的輸出是行緩衝的,並且當程序段發生故障時,它從不寫出來。

如果您添加 fflush(stdout);在每個printf之後,你會在segfault之前看到你的輸出。現在,在你的原始程序中,fcntl(fd,F_SETFL,0)的重點是什麼?呼叫?你想用它達到什麼目的?你是否試圖關閉非阻塞模式?如果你不打這個電話怎麼辦?

至於你的第二個測試,我看到你使用的是perror,但是再次缺少錯誤信息並不能告訴你程序沒有運行 - 它只是告訴你沒有任何錯誤消息,而且你還沒有刷新標準輸出,所以你永遠不會看到來自run_experiment的printf。

我還看到,在第二次測試中,您正在使用讀取模式執行fdopen,然後嘗試寫入該FILE指針。雖然這當然不應該崩潰,但它當然也不應該工作。

現在,在你的程序之外,你確定串口工作正常嗎?嘗試執行'cat>/dev/ttyS0'並查看會發生什麼,以確保它不會與硬件有關。

+0

'perror'寫入'stderr',它應該*是行緩衝的。如果這是一個正常的託管C環境,'stdout'也可以通過tty檢測進行行緩衝,但是對於這個嵌入式系統來說可能並非如此。 – zwol

+0

@Zack - 證據(來自run_experiment的printf未顯示)指示在此環境中緩衝的stdout IS行。 perror不應該是,但是如果他從其中一個人那裏得到一個錯誤,他不會擊中segfault。 –

+0

@Michael:你的評論非常詳細,非常好。我嘗試在每個'printf()'後添加fflush(stdout),但仍然沒有顯示,併發生段錯誤。我嘗試刪除'fcntl(fd,F_SETFL,0)'調用,但仍發生段錯誤。正如你所建議的那樣,我現在已經在第二次測試中更新了程序代碼,但是段錯誤仍然發生在程序開始時。我也嘗試過'cat>/dev/ttyS0',這確實很好。但是,如上所述,如果我從字符串中刪除'\ r'轉義序列,程序將運行! –

相關問題