Segfault由于C文件中的一行代码而发生,并且整个程序不运行

Nic*_*nar 5 c file-io arm serial-port segmentation-fault

我已经创建了一个C程序来写入嵌入式ARM系统上的串行端口(/ dev/ttyS0).在嵌入式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系统上运行.如果删除该行,程序将运行.

这是一个完整的代码列表,复制问题:

编辑:我现在关闭错误文件,如下面的评论中所示.

#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;
} 
Run Code Online (Sandbox Code Playgroud)

ARM系统上此程序的输出如下:

Segmentation fault
Run Code Online (Sandbox Code Playgroud)

但是,如果我删除上面列出的行并重新编译该程序,问题就会消失,输出如下:

Starting program
fd = 3
Now writing to serial port
Run Code Online (Sandbox Code Playgroud)

这里可能出现什么问题,我该如何调试问题?这会是代码,交叉编译器编译器或操作系统版本的问题吗?

打开文件时,我还尝试了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;
}
Run Code Online (Sandbox Code Playgroud)

这是非常神秘的,因为使用我编写的其他程序,我可以write()以类似的方式使用该函数来写入sysfs文件,没有任何问题.

但是,如果程序完全在同一个结构中,那么我就无法写入/ dev/null.

但我可以使用完全相同的程序成功写入sysfs文件!

如果段错误发生在函数中的特定行,那么我会假设函数调用将导致段错误.但是,完整的程序不运行!

更新:为了提供更多信息,以下是用于在ARM系统上构建的交叉编译器信息:

$ arm-linux-gcc --v使用内置规范.目标:臂未知-Linux的uclibcgnueabi配置有:/media/RESEARCH/SAS2-version2/device-system/buildroot/buildroot-2011.08/output/toolchain/gcc-4.3.6/configure前缀= /媒体/研究/SAS2-version2/device-system/buildroot/buildroot-2011.08/output/host/usr --build = x86_64的未知-Linux的GNU主机= x86_64的未知-Linux的GNU --target =手臂-unknown- Linux的uclibcgnueabi --enable语言= C,C++ --with-SYSROOT = /媒体/研究/ SAS2-版本2 /设备系统/ buildroot的/ buildroot的-2011.08 /输出/主机的/ usr /臂未知-Linux的uclibcgnueabi/SYSROOT --with-集结时的工具= /媒体/研究/ SAS2-版本2 /设备系统/ buildroot的/ buildroot的-2011.08 /输出/主机的/ usr /臂未知-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/build root-2011.08/output/host/usr --disable-nls --enable-threads --disable-decimal-float --with-float = soft --with-abi = aapcs-linux --with-arch = armv5te - -with调= ARM926EJ-S - 禁用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
Run Code Online (Sandbox Code Playgroud)

更新:使用下面的注释和答案中给出的调试建议,我发现segfault是由\r字符串中包含转义序列引起的.由于某些奇怪的原因,编译器不喜欢\r转义序列,并且会在不运行代码的情况下导致段错误.

如果\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;
} 
Run Code Online (Sandbox Code Playgroud)

编辑:除此之外,使用它更好:

ret = write( fd, msg, sizeof(msg) );
Run Code Online (Sandbox Code Playgroud)

或者它是否更好用:

ret = write( fd, msg, strlen(msg) );
Run Code Online (Sandbox Code Playgroud)

哪个更好?使用sizeof()或strlen()更好吗?看来字符串中的某些数据被截断,而不是使用sizeof()函数写入串行端口.

正如我从Pavel的评论中所理解的那样,最好使用strlen()if msg声明为char*.

而且,当转义序列\r用于写入tty 时,gcc似乎没有创建适当的二进制文件.

参考上面帖子中给出的最后一个测试程序,以下代码行导致不运行程序的段错误:

char *msg = "test\r\n";
Run Code Online (Sandbox Code Playgroud)

正如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)
Run Code Online (Sandbox Code Playgroud)

但是,如果我将单行代码更改为以下内容,则二进制文件将编译并正常运行.请注意,\r转义序列丢失:

char *msg = "test\n";
Run Code Online (Sandbox Code Playgroud)

以下是更改单行代码后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)
Run Code Online (Sandbox Code Playgroud)

更新:

正如Zack在下面的答案中所建议的那样,我现在在嵌入式Linux系统上运行了一个测试程序.虽然Zack提供了在嵌入式系统上运行的详细脚本,但由于缺少根文件系统中安装的开发工具(编译器和头文件),我无法运行该脚本.我只是编译了Zack在脚本中提供的好的测试程序并使用了strace实用程序,而不是安装这些工具.strace实用程序在嵌入式系统上运行.

最后,我想我明白发生了什么.

使用SPI到以太网桥(KSZ8851SNL)将错误的二进制文件通过FTP传输到嵌入式系统.Linux内核中有一个KSZ8851SNL的驱动程序.

看来Linux内核驱动程序,嵌入式系统上运行的proftpd服务器软件或实际硬件本身(KSZ8851SNL)都在某种程度上破坏了二进制文件.二进制文件在嵌入式系统上运行良好.

以下是通过以太网串行链路传输到嵌入式Linux系统的testz二进制文件的strace输出:

二进制测试错误:

# 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
#
Run Code Online (Sandbox Code Playgroud)

这是在SD卡上传输到嵌入式Linux系统的testz二进制文件的strace输出:

好的二元测试:

#  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)                           = ?
Run Code Online (Sandbox Code Playgroud)

zwo*_*wol 9

编辑:继续阅读血腥细节,但快速回答是,您的FTP客户端正在破坏您的程序.这是FTP的一个故意功能,可以通过binaryget whatever或之前键入FTP提示来关闭它put whatever.如果您使用的是图形化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:
Run Code Online (Sandbox Code Playgroud)

实际上,好的和坏的目标文件之间只有两个字节不同.(你误解了我的要求 "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.__|
Run Code Online (Sandbox Code Playgroud)

第一个区别是应该存在的差异: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|
Run Code Online (Sandbox Code Playgroud)

相同的两个差异,可执行文件中的不同偏移量.这也是应该的.我们也可以排除链接器中的错误(如果它搞砸了字符串的地址,它必须在两个可执行文件中以相同的方式将它搞砸,它们都应该崩溃).

此时我认为我们正在查看C库或内核中的错误.为了进一步确定,我希望你尝试这个测试脚本.sh testz.sh在ARM板上运行它,并向我们发送完整的输出.

#! /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
Run Code Online (Sandbox Code Playgroud)

我看过你提供的损坏的二进制文件,现在我知道出了什么问题.

$ 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
Run Code Online (Sandbox Code Playgroud)

忽略奇怪的日期戳; 看看-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...............|
Run Code Online (Sandbox Code Playgroud)

文件传输正在用(仅)替换0d 0a(即\r\n)序列.这会导致文件中此点之后的所有内容从其应有的位置移位四个字节.代码在此之前,内核所看到的所有ELF头也是如此,这就是为什么你没有得到的0a\n

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

从测试脚本; 相反,你在动态加载器中得到一个段错误,因为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
Run Code Online (Sandbox Code Playgroud)

对比:

$ 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
Run Code Online (Sandbox Code Playgroud)

调试信息也在置换后,这就是为什么gdb不喜欢这个程序.


那为什么这个非常特别的腐败?这不是任何事情的错误; 它是FTP 客户端的一个故意功能,默认以"文本模式"传输文件,这意味着(除其他外)它将DOS样式的行结尾(\r\n)转换为Unix风格(\n).因为如果这是1991年,那就是你想要的,并且你正在将IBM PC上的文本文件传输到机构文件服务器.即使您正在移动文本文件,它现在基本上也不是现在想要的东西.幸运的是,您可以将其关闭:只需binary在文件传输命令之前键入FTP提示符.*Un*幸运的是,据我所知,没有办法制造那根棍子 ; 你必须每次都这样做.我建议切换到scp,它总是逐字传输文件,也更容易从构建自动化操作.

  • 优秀的分析.我甚至不怀疑FTP是OP工具链的一部分.这就像西班牙的宗教裁判...... (2认同)
  • 这绝对是我在这里看到的最奇特的问题之一. (2认同)