【问题标题】:What are .LX routine tags for in x86-64 asm?x86-64 asm 中的 .LX 例程标签是什么?
【发布时间】:2020-01-03 15:29:34
【问题描述】:

我已尝试在网上搜索此内容,但没有找到任何答案。我正在研究汇编条件跳转并正在使用这个 C 例程:

long absdiff (long x, long y) {
    long result;

    if (x > y)
        result = x-y;
    else
        result = y-x;

    return result;
}

我的笔记说它返回一个类似这样的 asm 代码:​​

absdiff:
    cmpq %rsi, %rdi
    jle  .L4
    movq %rdi, %rax
    subq %rsi, %rax
    ret
.L4:
    movq %rsi, %rax
    subq %rdi, %rax
    ret

据我所知,如果x <= y,例程将跳转到.L4,然后从该跳转返回到下一条指令并继续直到ret,我知道这是错误的。由于%rax 是用.L4 编写的,我认为它的ret 适用于整个例程,而不是跳转到的那个,但我在使用gdb 调试C 例程时也看到了类似这样的代码:

0x1119 <absdiff>     mov   %rdi,%rax
0x111c <absdiff+3>   cmp   %rsi,%rdi
0x111f <absdiff+6>   jle   0x1125 <absdiff+12>
0x1121 <absdiff+8>   sub   %rsi,%rax
0x1124 <absdiff+11>  retq
0x1125 <absdiff+12>  sub   %rdi,%rsi
0x1128 <absdiff+15>  mov   %rsi,%rax
0x112b <absdiff+18>  retq

在这里,我了解到例程在不同的点上返回,就像您在 C 例程上编写不同的返回一样。所以我的问题是:汇编语言中.LX 例程标记的含义是什么,与它们跳转到的例程有什么关系?

【问题讨论】:

  • 如果满足比较或采用其他代码路径,则代码进行比较跳转一个代码路径是 return x - y 另一个是 return y - x,看起来很好,有什么问题? LX 是为编译器生成的汇编语言创建的标签,它是一个通用标签名称,理想情况下不会与用户标签冲突,为了进行跳转,汇编语言需要一个通常使用标签而不是绝对偏移量的目的地,让汇编器找出偏移量。
  • 代码运行良好,但我不明白.L4absdiff 的关系如何。我什么时候应该写一个.LX 例程,什么时候应该写一个不同的例程? .L4absdiff 的一部分吗?
  • 这些是标签,而不是标签。处理器不知道什么是例程。跳转到给定地址,仅此而已。
  • 在这种情况下反汇编时,反汇编程序和/或二进制文件不再保留 .L4 标签,因此在这种情况下使用最近的标签和偏移量。一个可以产生另一个,如果它被编译,可能会产生。 .L 之后的数字取决于当时该代码中有多少标签,汇编器会不断生成唯一的标签。
  • 我不知道它是否是点,我怀疑,但如果您自己编写该代码并使用您自己的标签 foo_bar:标签很可能在二进制文件和反汇编程序中会使用它而不是 absdiff

标签: assembly x86 subroutine


【解决方案1】:

jle 指令执行跳转而不是调用。这直接转移控制,而不将返回地址压入堆栈:它就像 C 中的 goto,而不是调用。这意味着下面的ret 返回到absdiff 的调用者,因为它仍然是栈顶的返回地址。

【讨论】:

    【解决方案2】:

    .L4 之类的标签名称由编译器自动编号,每次它需要一个分支目标时。

    Clang 通过计算基本块来对其标签进行编号(因此第一个函数中的第 4 个基本块将具有类似 .LBB0_3 的标签名称),但我认为 GCC 仅在发出(第一个)跳转指令时增加其标签计数器跳到那里。

    这就是为什么标签本身在函数中不是严格按数字顺序递增的,而只是在文件中的整体。

    GCC 永远不会跨越函数边界跳转到这些内部标签。


    .Lname 标签是本地标签,不会进入目标文件/可执行文件的符号表。这就是为什么您在调试器中看不到它们,只有函数名称。

    我认为它的 ret 适用于整个例程,而不是跳到的那个,

    是的。 ret 不是魔法。 ret 就是 pop %ripjne 不推送返回地址,所以它不是函数调用,只是一个普通的分支。

    顺便说一句,函数有两种方法称为“尾部复制”优化。他们没有让一条路径跳转到另一条路径,而是都进行了任何清理和ret。执行将通过一个或另一个,而不是两个。

    但我在使用 gdb 调试 C 例程时也看到过类似这样的代码:

    “但是”?这正是你从汇编 + 链接编译器生成的 asm 中得到的。

    符号命名的分支目标(在这种情况下由汇编程序)替换为数字目标地址。 (实际上编码为相对位移,例如jcc rel8。)汇编器能够在不等待链接时间的情况下执行此操作,因为跳转与目标位于同一文件中,并且是相对的。

    【讨论】:

      【解决方案3】:
      one:
          b .L77
          nop
          nop
      .L77:
          b two
          nop
          nop
      two:
          b .three
          nop
          nop
          nop
      .three:
          nop
          nop
          
      
      
      Disassembly of section .text:
      
      00000000 <one>:
         0:   ea000001    b   c <one+0xc>
         4:   e1a00000    nop         ; (mov r0, r0)
         8:   e1a00000    nop         ; (mov r0, r0)
         c:   ea000001    b   18 <two>
        10:   e1a00000    nop         ; (mov r0, r0)
        14:   e1a00000    nop         ; (mov r0, r0)
      
      00000018 <two>:
        18:   ea000002    b   28 <.three>
        1c:   e1a00000    nop         ; (mov r0, r0)
        20:   e1a00000    nop         ; (mov r0, r0)
        24:   e1a00000    nop         ; (mov r0, r0)
      
      00000028 <.three>:
        28:   e1a00000    nop         ; (mov r0, r0)
        2c:   e1a00000    nop         ; (mov r0, r0)
      

      编译器生成程序集,程序集被提供给汇编器并变成一个对象。编译器将需要生成独立于您创建的标签(函数名称等)的标签,因此这个特定的使用 .Ln 其中 n 是一个数字,它在该汇编语言程序/模块/文件中是唯一的。

      这个汇编器清楚地在二进制/对象中保留了其他非 .Ln 标签,但丢弃了 .Ln 标签。然后你使用一个单独的工具,一个反汇编器,它选择它想要如何表示机器代码。在这种情况下,我们得到一个绝对地址 b c 意味着 b 0xC 以及一个助手,0xC 位于距最近标签的偏移量 0xC 处。显然,简单地在标签前面放一个点并不是让它消失的方法。

      但是这个

      one:
          b .L77
          nop
          nop
      .L77:
          b two
          nop
          nop
      two:
          b .Lthree
          nop
          nop
          nop
      .Lthree:
          nop
          nop
          
      
      00000000 <one>:
         0:   ea000001    b   c <one+0xc>
         4:   e1a00000    nop         ; (mov r0, r0)
         8:   e1a00000    nop         ; (mov r0, r0)
         c:   ea000001    b   18 <two>
        10:   e1a00000    nop         ; (mov r0, r0)
        14:   e1a00000    nop         ; (mov r0, r0)
      
      00000018 <two>:
        18:   ea000002    b   28 <two+0x10>
        1c:   e1a00000    nop         ; (mov r0, r0)
        20:   e1a00000    nop         ; (mov r0, r0)
        24:   e1a00000    nop         ; (mov r0, r0)
        28:   e1a00000    nop         ; (mov r0, r0)
        2c:   e1a00000    nop         ; (mov r0, r0)
      

      确实让它消失了,所以人们会认为 .Lx 是一个有效的标签名称,但汇编器没有将它放在输出二进制文件的符号表中。代码是正确的,它只是没有汇编语言的所有标签,这很好,机器代码没有标签,它只是人类可读的东西。这种机制允许工具链轻松地为每个文件生成中间标签,而不必神奇地找出如何避免冲突(这是不可能的)。

      这个汇编器(family,gnu assembler,gas)也有这个功能,编译器不使用,但一些懒惰的编码器使用:

      1:
          b 1f
          b 1b
          b 2f
      1:
          nop
          nop
      2:
      
      
      00000000 <.text>:
         0:   ea000001    b   c <.text+0xc>
         4:   eafffffd    b   0 <.text>
         8:   ea000001    b   14 <.text+0x14>
         c:   e1a00000    nop         ; (mov r0, r0)
        10:   e1a00000    nop         ; (mov r0, r0)
      

      1f 表示标签 1:在代码中向前 1b 表示标签 1 在代码中向后(该方向的第一次出现)。您可以使用相同的标签名称 1: 或其中的一小部分 1: 2: 3: 全部通过您的代码用于与 .Lx 相同的目的,但您甚至不必具有唯一标签。也许这适用于我没有尝试过的数字以外的东西。

      【讨论】:

      • 不,这不是 x86,它是 ARM 更容易反汇编,gnu 汇编器对两个目标的工作方式相同。
      猜你喜欢
      • 2011-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-22
      • 1970-01-01
      • 2012-08-15
      • 1970-01-01
      相关资源
      最近更新 更多