下面的程序集对下面的.c文件做了什么

问题描述 投票:0回答:2

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

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"
c assembly sparc
2个回答
8
投票

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

我想我们是一行一行地走? 我将跳过一些无趣的样板文件。

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

这是您在

printf
中使用的字符串常量(非常明显,我知道!)需要注意的重要事项是它位于
.rodata
部分(部分是最终可执行映像的划分;这部分用于“读取” -only data”并且实际上在运行时是不可变的)并且它被赋予了 label
.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
,这是第一个 outgoing 参数寄存器。 (该函数的参数位于incoming参数寄存器中。)

        mov     20, %o1

将立即数 100 加载到第二个传出参数寄存器

%o1
中。 这是由
((foo *)0)+1
计算得出的值。它是 20,因为您的
struct foo
是 20 字节长(五个 4 字节
int
),并且您要求数组中从地址 0 开始的第二个。

顺便说一下,只有当基指针的地址处实际上有一个足够大的数组时,计算指针的偏移量才在 C 中得到明确的定义;

((foo *)0)
是一个空指针,因此那里没有数组,因此表达式
((foo *)0)+1
在技术上具有未定义的行为。 GCC 4.2.1,针对托管 SPARC,恰好将其解释为“假装在地址 0 处有一个任意大的
foo
数组,并计算数组成员 1 的预期偏移量”,但其他(尤其是较新的)编译器可能会做一些完全不同的事情。

        call    printf, 0
        nop

致电

printf
。 我不记得零是做什么用的。
call
指令有一个延迟槽(再次阅读架构手册),其中填充了一条不执行任何操作的指令,
nop

        return  %i7+8
        nop

跳转到寄存器

%i7
中的地址加8。 这具有从当前函数返回的效果。

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


1
投票

程序集调用

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

© www.soinside.com 2019 - 2024. All rights reserved.