【问题标题】:Not getting Segmentation Fault in C在 C 中没有出现分段错误
【发布时间】:2012-01-14 11:13:27
【问题描述】:

这里是c代码:

char **s;
s[334]=strdup("test");
printf("%s\n",s[334]);`

我知道 strdup 分配了“test”,但是我们将指针指向字符串“test”的情况 s[334] 没有分配,但是,这段代码就像一个魅力

【问题讨论】:

  • 具体来说,s 是在堆栈上分配的,因此它实际上可能包含一个合法的指针,这取决于您之前在其他函数中所做的操作。
  • 没有其他功能,主要只有这三行。
  • 你主要给了什么签名? int main(),或int main(int argc, char ** argv)
  • 使用的签名是 int main(),使用 gcc 编译
  • 好吧,你说得对,这里发生了奇怪的...

标签: c segmentation-fault


【解决方案1】:

您的代码表现出未定义的行为。这确实意味着它会崩溃。这意味着你无法预测会发生什么。

在这种情况下,很可能会发生崩溃,但完全不能保证。

【讨论】:

    【解决方案2】:

    “未定义的行为”并不意味着你会得到一个段错误,它意味着你可能会得到一个段错误。符合规范的实现也可能决定显示小狗的 ASCII 艺术。

    您可能想使用Valgrind 之类的工具检查此代码。

    【讨论】:

    • Valgrind 没有发现这个错误。这比我想象的更奇怪。我想我会提出另一个问题。
    • 这也取决于优化级别...这是一些有趣的未定义行为,因为s 始终是0
    • 只要索引(此处为 334)低于 4098(可能是寻址空间的限制),它就可以工作
    • 我认为 OP 应该向编译器提交一个错误,因为他没有得到那个 ASCII 小狗。
    • 如果s 为零,则s[334] 是某个变量(甚至是可执行代码)的地址,具体取决于内存布局,它可能恰好包含指向可写的有效指针内存区域,被垃圾覆盖。要确切了解会发生什么,您需要知道该程序的内存布局以及所述内存的内容。一个有趣的项目,也许是一个周末的缓慢下午。 (这种分析是您在解释漏洞利用时所看到的)。
    【解决方案3】:
    1. 如果您访问未初始化的内存,您并不总是会遇到分段错误。

    2. 您确实在此处访问了未初始化的内存。

    【讨论】:

    • 不是未分配的内存,是未初始化的内存,具体来说,s。无论s 碰巧指向什么,都可以分配内存。
    • 其中“未分配内存”可能意味着内存恰好被分配给其他东西。
    【解决方案4】:

    编译器对我们来说太聪明了!它知道printf("%s\n", some_string)puts(some_string)完全一样,所以可以简化

    char **s;
    s[334]=strdup("test");
    printf("%s\n",s[334]);
    

    进入

    char **s;
    s[334]=strdup("test");
    puts(s[334]);
    

    然后(假设没有UB)再次等价于

    puts(strdup("test"));
    

    因此,碰巧没有发生段错误(这次)。

    【讨论】:

    • 编译器的智能在这里没有任何区别。此外,对s[334] 的赋值不能直接删除。
    • @vonbrand - 如果您查看 Daniel Fisher 回答中的程序集,那正是它所做的。
    【解决方案5】:

    我得到一个没有优化的段错误,但是当使用优化编译时,gcc 根本不会打扰s,它会作为死代码被消除。

    gcc -Os -S:

    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $.LC0, %edi     # .LC0 is where "test" is at
    call    strdup
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    movq    %rax, %rdi
    jmp     puts
    .cfi_endproc
    

    gcc -S -O(-O2、-O3 相同):

    .LFB23:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        movl    $5, %edi
        call    malloc
        movq    %rax, %rdi
        testq   %rax, %rax
        je      .L2
        movl    $1953719668, (%rax)
        movb    $0, 4(%rax)
    .L2:
        call    puts
        addq    $8, %rsp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
    

    【讨论】:

    • 我对汇编语言不是很熟悉,你能解释一下gcc在优化编译时的行为吗
    • 我对汇编也不是很熟悉,但据我所知,-Os 可以:从堆栈指针中减去 8,将字符串的地址移入寄存器,strdup 字符串,将 8 加回堆栈指针,将从 strdup 获得的指针移动到另一个寄存器,跳转到 puts 输出字符串。 -O1/2/3 执行:从堆栈指针中减去 8,将文字 5 移动到寄存器 %edi,malloc(5 个字节),将获得的指针移动到 %rdi,检查 NULL,移动文字“test”(作为 4 字节little-endian integer) 到分配的内存,将 0 终止符移到末尾,调用 puts,将 8 添加回堆栈指针。
    • 优化器将程序转换为puts(strdup("test"));
    • @BoPersson 是的,感谢您的确认。但是,我完全不明白为什么它会打扰strdup()。应该可以做到puts("test");,不是吗?
    • @Daniel - 是的,但这可能不够普遍,无法放入优化器中? printf("%s\n"常见的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-01
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    • 2020-09-30
    • 2015-06-19
    • 1970-01-01
    相关资源
    最近更新 更多