【问题标题】:What does the following assembly does for the following .c file以下程序集对以下 .c 文件有什么作用
【发布时间】:2012-02-15 04:43:39
【问题描述】:

我已经写了下面的代码,你能解释一下这里的程序集告诉我什么吗?

typedef struct
{
    int abcd[5];
} hh;

void main()
{
    printf("%d", ((hh*)0)+1);
}  

组装:

        .file   "aa.c"
        .section        ".rodata"
        .align 8
.LLC0:

        .asciz  "%d\n"
        .section        ".text"
        .align 4
        .global main
        .type   main, #function
        .proc   020
main:

        save    %sp, -112, %sp
        sethi   %hi(.LLC0), %g1
        or      %g1, %lo(.LLC0), %o0
        mov     20, %o1
        call    printf, 0
         nop
        return  %i7+8
         nop
        .size   main, .-main
        .ident  "GCC: (GNU) 4.2.1"

【问题讨论】:

  • SPARC。 save 指令和名为 %gN%oN%iN 的寄存器是一个死的赠品。
  • 由于您使用的是gcc,您可能想尝试使用标志-g -Wa,-aldh 进行编译,这将打印一个将高级源代码与生成的程序集交错的列表。
  • 和/或 -fverbose-asm 用操作数的高级 var 名称注释每一行。

标签: c assembly compiler-construction sparc


【解决方案1】:

哇哦,SPARC 汇编语言,我已经 没见过它了。

我猜我们一行一行地走?我将跳过一些无趣的样板。

        .section        ".rodata"
        .align 8
.LLC0:
        .asciz  "%d\n"

这是您在printf 中使用的字符串常量(很明显,我知道!)要注意的重要事项是它在.rodata 部分中(部分是最终可执行映像的分区;这个用于“只读数据”,实际上在运行时是不可变的)并且它被赋予了 标签 .LLC0。以点开头的标签是对象文件的私有标签。稍后,编译器在加载字符串常量的地址时会引用该标签。

        .section        ".text"
        .align 4
        .global main
        .type   main, #function
        .proc   020
main:

.text 是实际机器代码的部分。这是用于定义名为main 的全局函数的样板头文件,它在汇编级别与任何其他函数没有什么不同(在C 中——在C++ 中不一定如此)。我不记得.proc 020 做了什么。

        save    %sp, -112, %sp

保存上一个寄存器窗口,向下调整堆栈指针。如果你不知道什么是寄存器窗口,你需要阅读架构手册:http://sparc.org/wp-content/uploads/2014/01/v8.pdf.gz。 (V8 是 SPARC 的最后一个 32 位迭代,V9 是第一个 64 位迭代。这似乎是 32 位代码。)

        sethi   %hi(.LLC0), %g1
        or      %g1, %lo(.LLC0), %o0

这两个指令序列具有将地址.LLC0(这是您的字符串常量)加载到寄存器%o0 的净效果,这是第一个传出 参数寄存器。 (这个函数的参数在传入参数寄存器中。)

        mov     20, %o1

将立即数 100 加载到第二个传出参数寄存器 %o1 中。这是((foo *)0)+1 计算的值。它是 20,因为您的 struct foo 有 20 个字节长(五个 4 字节 ints),并且您要求从地址 0 开始的数组中的第二个字节。

顺便说一句,只有当基指针的地址处实际上有一个足够大的数组时,才在 C 中明确定义了从指针计算偏移量; ((foo *)0) 是一个空指针,所以那里没有数组,所以表达式 ((foo *)0)+1 在技术上具有未定义的行为。针对托管 SPARC 的 GCC 4.2.1 恰好将其解释为“假装在地址 0 处有一个任意大的 foos 数组并计算数组成员 1 的预期偏移量”,但其他(尤其是较新的)编译器可能做一些完全不同的事情。

        call    printf, 0
        nop

致电printf。我不记得零是干什么用的。 call 指令有一个延迟槽(同样,请阅读架构手册),其中填充了无操作指令nop

        return  %i7+8
        nop

跳转到寄存器%i7 中的地址加八。这具有从当前函数返回的效果。

return也有一个延迟槽,用另一个nop填充。在这个延迟槽中应该有一个restore 指令,与函数顶部的save 匹配,以便main 的调用者得到它的寄存器窗口。我不知道为什么它不在那里。 cmets 中的讨论讨论了 main 可能不需要弹出注册窗口,和/或您已将 main 声明为 void main() (不保证适用于任何 C 实现,除非其文档明确说明,并且是 always 不好的风格)...但是在 SPARC 上推送而不弹出注册窗口是一件很麻烦的事情,我认为这两种解释都没有说服力。我什至可以称它为编译器错误。

【讨论】:

  • @Zack: main() 的特殊之处在于它的调用者对保留状态没有任何期望——包括注册窗口。 main() 的调用者总是只会在之后退出(只是dis 生成的可执行文件并查找__start)因此,在这种特定情况下不需要restore。可能在 SPARC ABI 的某个地方提到了这一点......
  • @FrankH。通常_start 的最后一行是exit(main(argc, argv, environ)) 或汇编中的等价物。是否需要弹出注册窗口才能让exit 的参数出现在正确的位置? (这可能是个愚蠢的问题,我已经很久没有碰过 SPARC 盒子了。)
  • @Zack:注意错误的声明void main() - 这能回答问题吗?
【解决方案2】:

程序集调用printf,传递您的文本缓冲区和堆栈上的数字20(这是您以迂回的方式要求的)。

【讨论】:

  • 我从中得到 20 个:mov 20, %o1
  • ...是的,这不是它过去所说的。对不起,噪音。 +1 更短的答案:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-18
  • 2014-10-13
  • 2019-07-06
  • 1970-01-01
  • 2010-09-18
相关资源
最近更新 更多