各位大佬,求帮分析一下这段 C 代码 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
mrzys
V2EX    程序员

各位大佬,求帮分析一下这段 C 代码

  •  
      mrzys 2018-03-22 12:50:38 +08:00 4001 次点击
    这是一个创建于 2763 天前的主题,其中的信息可能已经有所发展或是发生改变。

    C 小白一个,求各位大佬关照。问题代码见下:

    #include <stdio.h> int main() { long i; long j; char *ch; // 这里的确是需要初始化 scanf("%s", ch); i = 0; // 如果把这行注释掉,程序不会报错 j = 0; // } 

    执行结果:

    root@ubuntu:~# ./a.out v2ex Segmentation fault 

    但是把下面给ij赋值的语句注释掉,或者只注释其中一条,却不会报错了:

    root@ubuntu:~# ./a.out v2ex 

    我是用objdump -d main.o查看反汇编的代码:

    0000000000400546 <main>: 400546: 48 83 ec 08 sub $0x8,%rsp 40054a: be 00 00 00 00 mov $0x0,%esi 40054f: bf f4 05 40 00 mov $0x4005f4,%edi 400554: b8 00 00 00 00 mov $0x0,%eax 400559: e8 d2 fe ff ff callq 400430 <__isoc99_scanf@plt> 40055e: b8 00 00 00 00 mov $0x0,%eax 400563: 48 83 c4 08 add $0x8,%rsp 400567: c3 retq 400568: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 

    发现scanfch指针的地址赋值为0x0,这里的确是有问题,但是我想不通scanf下面的语句为什么会影响到程序的执行。

    求各位大佬指教啊。。。

    32 条回复    2018-03-24 20:01:49 +08:00
    lance6716276
        1
    lance6716276  
       2018-03-22 12:55:19 +08:00 via Android
    先把代码写标准了再去 debug
    shuax
        2
    shuax  
       2018-03-22 12:57:03 +08:00
    char *ch 只是一个指针,都没有给他固定空间,然后就越界了。
    yinanc
        3
    yinanc  
       2018-03-22 13:01:12 +08:00
    赋值给未分配空间的指针时情况就不可控了,后面发生什么都有可能
    shsdust
        4
    shsdust  
       2018-03-22 13:06:59 +08:00
    不是指明了 Segmentation fault 吗?表示 CPU 试图访问无法定址的块,明显是因为指针没被分配空间,既然到这里已经出现了这种错误,下面的程序报错很正常的
    shsdust
        5
    shsdust  
       2018-03-22 13:07:39 +08:00
    还有,编译是一门玄学
    mrzys
        6
    mrzys  
    OP
       2018-03-22 13:16:19 +08:00
    @shsdust 但是这样并不会报错:
    ```
    #include <stdio.h>

    int main() {
    long i;
    long j;
    char *ch;
    scanf("%s", ch);
    i = 0;
    //j = 0;
    }
    ~
    ```
    这就是令人奇怪的地方。` scanf`后面的代码会影响到程序会不会发生异常退出。
    jiutai21
        7
    jiutai21  
       2018-03-22 13:19:27 +08:00
    未定义的行为把,换个平台 /编译器结果不一定的
    tomychen
        8
    tomychen  
       2018-03-22 13:20:09 +08:00
    1.ch 没空间
    2.注释掉那行后没出错有没有可能是因为刚好 scanf 到 ch 的值刚好复盖到了第二个 long 上,所以没报错,你可以试动态调试一下。或者简单的把输入内容调整>sizeof(long)。
    3. @shsdust 楼上说了玄学,这种东西代码在你的环境不出错不表示任何环境不出错,c 的指针野起来不是人能预见的。
    liuzhedash
        9
    liuzhedash  
       2018-03-22 14:14:05 +08:00
    @mrzys #6
    兄弟,这样写是否报错并不是你想的那种因果关系,建议阅读《深入理解计算机系统》的第三章。
    doun
        10
    doun  
       2018-03-22 14:17:14 +08:00 via Android
    大概因为 scanf 下面的 int i,j 编译后,也是挪到栈上的吧
    mrzys
        11
    mrzys  
    OP
       2018-03-22 14:47:40 +08:00
    @liuzhedash 就在阅读 csapp,但是对于内存这一块还是很模糊。第 7 章 linker 中说了局部变量是在执行的时候在 stack 上动态分配的,但是我反汇编之后,查看不到这段代码中的 i 和 j 的赋值指令。只有`sub $0x8,%rsp`,栈指针向下增加了 8 个字节,函数执行完毕返回之前执行`add $0x8,%rsp`,栈指针向上移动了 8 个字节,栈指针完全没有被影响。
    lingdux
        12
    lingdux  
       2018-03-22 14:52:36 +08:00
    那里的野指针需要申请空间,大小要足够容纳输入的内容。
    注释掉那句不发生异常只是堆栈压栈出栈的巧合而已,程序并没有按照你的预期执行。

    这种问题动态调式,注意一下堆栈和内存,一目了然。
    7 楼不懂别瞎说误导新人。
    mrzys
        13
    mrzys  
    OP
       2018-03-22 14:57:25 +08:00
    @lingdux 谢谢大佬,晚上回去动态调试一下。
    ysc3839
        14
    ysc3839  
       2018-03-22 15:01:40 +08:00 via Android
    @lingdux 想问一下 7 楼说的怎么误导了?
    lingdux
        15
    lingdux  
       2018-03-22 15:05:32 +08:00
    @ysc3839

    未定义的行为很扯淡
    lingdux
        16
    lingdux  
       2018-03-22 15:09:52 +08:00   1
    @mrzys
    这个问题让我想起来《 0day 安全:软件漏洞分析技术》里面一个例子
    虽然那个是 win 的,但是一样的原理。
    2 . 2 修改邻接变量中,修改了相邻的变量,填补了被破坏的堆栈,导致程序改变了流程实现了破解 CrackMe。
    pkookp8
        17
    pkookp8  
       2018-03-22 15:10:58 +08:00 via Android
    尝试了 x86 和 arm,都不会出错
    gcc5.3.1
    armgcc4.4.1
    编译方式
    $(CC) src.c -o err.bin
    不加其他编译参数
    pkookp8
        18
    pkookp8  
       2018-03-22 15:15:22 +08:00 via Android
    @pkookp8 尝试增加-g 和-O0 也没用。。。。
    ysc3839
        19
    ysc3839  
       2018-03-22 15:16:52 +08:00 via Android
    @lingdux 为什么这么说呢?
    lingdux
        20
    lingdux  
       2018-03-22 15:38:38 +08:00
    @ysc3839
    别问了,不想回复你了,感觉挺没意思的。
    mengyaoss77
        21
    mengyaoss77  
       2018-03-22 15:50:31 +08:00
    说是小白,玩的东西完全不像小白啊,直接反汇编。
    反汇编看不懂,我只知道这个代码是危险的。就像 gets()一样
    gunavy
        22
    gunavy  
       2018-03-22 16:08:05 +08:00
    就像 @pkookp8 做的实验一样,平台编译器都会有影响,对堆栈的划分和组织也不一样。要知道哪里的问题,只能汇编调试。
    neoblackcap
        23
    neoblackcap  
       2018-03-22 16:11:51 +08:00 via iPhone   1
    为什么要研究 undefined behavior?这个发生什么事系统都是不作保证的
    pkookp8
        24
    pkookp8  
       2018-03-22 16:16:05 +08:00 via Android
    @pkookp8 试了 i386 的 gcc4.4.6
    必定段错误。。。无法复现你说的屏蔽或不屏蔽现象不同
    能否把出问题和不出问题的汇编都贴上来?
    doun
        25
    doun  
       2018-03-22 16:58:48 +08:00 via Android
    @mrzys 下 8 个字节就是分配两个 int 啊兄弟
    dummytaurus
        26
    dummytaurus  
       2018-03-22 17:35:40 +08:00
    这个只是巧合。进入 main 的时候,栈上第一个变量值是 nil,第二个指向栈上,第三个指向错误地址。所以 scanf 处理第三个变量会 segfault,而处理第一个(scanf 应该是有对 nil 做特殊处理)和第二个变量值没有问题。
    注释前,gcc 无法优化,所以 ch 是第三个变量,指向错误地址。注释后,gcc 将优化掉 i 或者 j,不管怎样,ch 会变成第一个或者第二个变量,它的值都不会引起 scanf 崩溃。
    不过栈上的初始值肯定是和 crt 相关的,macOS 上面两个示例都会蹦
    tomychen
        27
    tomychen  
       2018-03-22 17:43:19 +08:00
    @mrzys #11 提到的

    你早点说你在刷题和题型,就不会引那么多人猜了...
    mrzys
        28
    mrzys  
    OP
       2018-03-24 19:40:20 +08:00
    @dummytaurus 我使用 gdb 打印了一下未初始化的指针的值,两次指针指向的地址不一样,没注释 i 和 j 的时候指针地址是合法的,注释其中一个的时候指针地址指向了 read-only code segment。的确和 crt 有关系。我怀疑是调用 main 之前已经使用了栈空间,栈上的值被上次的函数调用写入了数据,当调用 main 的时候,因为指针没有初始化,用的上次的值。
    mrzys
        29
    mrzys  
    OP
       2018-03-24 19:43:10 +08:00
    @pkookp8 我自己测试环境是 ubuntu x86-64,用 macOS 也测试了一下,-O0 的时候会报错,但是-O1 的时候不会报错。
    mrzys
        30
    mrzys  
    OP
       2018-03-24 19:48:20 +08:00
    @lingdux 大佬,我动态调试了一下,的确是一个巧合,未注释和注释的时候,指针的地址虽说有 8 个字节的偏差,但是正式因为这 8 个字节的偏差,导致指针指向的地址完全不一样,未注释的时候指针指向的地址指向了 code segment,注释后,指针指向的地址是合法的。
    mrzys
        31
    mrzys  
    OP
       2018-03-24 19:55:41 +08:00
    @tomychen 额,感觉没关系啊。我刚好写完了 rio,准备写代码测试的时候发现了这个蛋疼的问题。不过还好,解决了这个问题加深了对汇编和运行时堆栈的了解。
    mrzys
        32
    mrzys  
    OP
       2018-03-24 20:01:49 +08:00
    @pkookp8
    环境:4.13.0-37-generic gcc version 5.4.0 20160609

    ```
    两次未优化的汇编代码:
    ```
    Dump of assembler code for function main:
    0x0000000000400546 <+0>: push %rbp
    0x0000000000400547 <+1>: mov %rsp,%rbp
    0x000000000040054a <+4>: sub $0x10,%rsp
    0x000000000040054e <+8>: mov -0x10(%rbp),%rax
    0x0000000000400552 <+12>: mov %rax,%rsi
    0x0000000000400555 <+15>: mov $0x400604,%edi
    0x000000000040055a <+20>: mov $0x0,%eax
    0x000000000040055f <+25>: callq 0x400430 <__isoc99_scanf@plt>
    0x0000000000400564 <+30>: movq $0x0,-0x8(%rbp)
    0x000000000040056c <+38>: mov $0x0,%eax
    0x0000000000400571 <+43>: leaveq
    0x0000000000400572 <+44>: retq

    ```

    ```
    Dump of assembler code for function main:
    0x0000000000400546 <+0>: push %rbp
    0x0000000000400547 <+1>: mov %rsp,%rbp
    0x000000000040054a <+4>: sub $0x20,%rsp
    0x000000000040054e <+8>: mov -0x18(%rbp),%rax
    0x0000000000400552 <+12>: mov %rax,%rsi
    0x0000000000400555 <+15>: mov $0x400604,%edi
    0x000000000040055a <+20>: mov $0x0,%eax
    0x000000000040055f <+25>: callq 0x400430 <__isoc99_scanf@plt>
    0x0000000000400564 <+30>: movq $0x0,-0x10(%rbp)
    0x000000000040056c <+38>: movq $0x0,-0x8(%rbp)
    0x0000000000400574 <+46>: mov $0x0,%eax
    0x0000000000400579 <+51>: leaveq
    0x000000000040057a <+52>: retq
    End of assembler dump.

    ```
    -0x18(%rbp)和 -0x10(%rbp)的值,一个是非法的一个是合法的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3685 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 10:29 PVG 18:29 LAX 03:29 JFK 06:29
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86