【问题标题】:Understanding simple assember program [closed]理解简单的汇编程序[关闭]
【发布时间】:2014-11-02 02:55:00
【问题描述】:

我很久以前就练习过汇编程序,我想了解一个简单的程序(我从 C 代码生成汇编程序代码),它添加 2 个向量(实际上是 2 个数组)并将结果存储在另一个向量(输出数组)中.我的目标是学习矢量化。为此,我在 i7 核处理器上的 Debian Wheezy 下使用 gcc-4.9。

这里是 C 代码 sn-p(非矢量化版本):

#include <stdio.h>

#define SIZE 10000

void test(double *a, double *b, double *c)
{
  int i;

  for (i = 0; i < SIZE; i++)
  {
    c[i] = a[i] + b[i];
  }
}

int main()
{
 int i;
 double tab1[SIZE];
 double tab2[SIZE];
 double tab3[SIZE];

 for (i = 0; i < SIZE; i++)
    {
     tab1[i] = i;
     tab2[i] = i;
     tab3[i] = 0;
    }

 test(tab1, tab2, tab3);

 for (i = 0; i < SIZE; i++)
    printf(" tab3[%d] = %f\n", i, tab3[i]);

 return 0;
}

我使用 AT&T 语法生成汇编代码:

gcc -std=c99 -c main_no_vectorized.c -O3 -S -o main_no_vectorized.s

这是汇编代码:

    .file   "main_no_vectorized.c"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB0:
    .text
.LHOTB0:
    .p2align 4,,15
    .globl  test
    .type   test, @function
test:
.LFB3:
    .cfi_startproc
    leaq    16(%rdx), %rax
    leaq    16(%rsi), %rcx
    cmpq    %rax, %rsi
    setae   %r8b
    cmpq    %rcx, %rdx
    setae   %cl
    orb %cl, %r8b
    je  .L7
    cmpq    %rax, %rdi
    leaq    16(%rdi), %rax
    setae   %cl
    cmpq    %rax, %rdx
    setae   %al
    orb %al, %cl
    je  .L7
    testb   $8, %dil
    pushq   %r12
    .cfi_def_cfa_offset 16
    .cfi_offset 12, -16
    pushq   %rbp
    .cfi_def_cfa_offset 24
    .cfi_offset 6, -24
    pushq   %rbx
    .cfi_def_cfa_offset 32
    .cfi_offset 3, -32
    je  .L8
    movsd   (%rdi), %xmm0
    movl    $9998, %ebp
    movl    $4999, %r9d
    movl    $9999, %r12d
    movl    $1, %r8d
    movl    $1, %ebx
    addsd   (%rsi), %xmm0
    movsd   %xmm0, (%rdx)
.L3:
    salq    $3, %r8
    xorl    %eax, %eax
    xorl    %ecx, %ecx
    leaq    (%rdi,%r8), %r11
    leaq    (%rsi,%r8), %r10
    addq    %rdx, %r8
    .p2align 4,,10
    .p2align 3
.L4:
    movupd  (%r10,%rax), %xmm0
    addl    $1, %ecx
    addpd   (%r11,%rax), %xmm0
    movups  %xmm0, (%r8,%rax)
    addq    $16, %rax
    cmpl    %r9d, %ecx
    jb  .L4
    cmpl    %ebp, %r12d
    leal    (%rbx,%rbp), %eax
    je  .L1
    cltq
    movsd   (%rdi,%rax,8), %xmm0
    addsd   (%rsi,%rax,8), %xmm0
    movsd   %xmm0, (%rdx,%rax,8)
.L1:
    popq    %rbx
    .cfi_remember_state
    .cfi_restore 3
    .cfi_def_cfa_offset 24
    popq    %rbp
    .cfi_restore 6
    .cfi_def_cfa_offset 16
    popq    %r12
    .cfi_restore 12
    .cfi_def_cfa_offset 8
    ret
    .p2align 4,,10
    .p2align 3
.L8:
    .cfi_restore_state
    movl    $10000, %ebp
    movl    $5000, %r9d
    movl    $10000, %r12d
    xorl    %r8d, %r8d
    xorl    %ebx, %ebx
    jmp .L3
.L7:
    .cfi_def_cfa_offset 8
    .cfi_restore 3
    .cfi_restore 6
    .cfi_restore 12
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L2:
    movsd   (%rdi,%rax), %xmm0
    addsd   (%rsi,%rax), %xmm0
    movsd   %xmm0, (%rdx,%rax)
    addq    $8, %rax
    cmpq    $80000, %rax
    jne .L2
    rep ret
    .cfi_endproc
.LFE3:
    .size   test, .-test
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC3:
    .string " tab3[%d] = %f\n"
    .section    .text.unlikely
.LCOLDB4:
    .section    .text.startup,"ax",@progbits
.LHOTB4:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB4:
    .cfi_startproc
    pushq   %rbx
    .cfi_def_cfa_offset 16
    .cfi_offset 3, -16
    xorl    %eax, %eax
    subq    $240016, %rsp
    .cfi_def_cfa_offset 240032
    movdqa  .LC2(%rip), %xmm3
    leaq    32(%rsp), %rcx
    leaq    80032(%rsp), %rdx
    movdqa  .LC1(%rip), %xmm1
    .p2align 4,,10
    .p2align 3
.L21:
    pshufd  $238, %xmm1, %xmm0
    cvtdq2pd    %xmm1, %xmm2
    paddd   %xmm3, %xmm1
    movaps  %xmm2, 16(%rsp,%rax)
    cvtdq2pd    %xmm0, %xmm0
    movaps  %xmm2, 80016(%rsp,%rax)
    movaps  %xmm0, (%rcx,%rax)
    movaps  %xmm0, (%rdx,%rax)
    addq    $32, %rax
    cmpq    $80000, %rax
    jne .L21
    leaq    160016(%rsp), %rdi
    movl    $80000, %edx
    xorl    %esi, %esi
    call    memset
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L22:
    movapd  16(%rsp,%rax), %xmm0
    addpd   80016(%rsp,%rax), %xmm0
    movaps  %xmm0, 160016(%rsp,%rax)
    addq    $16, %rax
    cmpq    $80000, %rax
    jne .L22
    xorl    %ebx, %ebx
    .p2align 4,,10
    .p2align 3
.L23:
    movsd   160016(%rsp,%rbx,8), %xmm4
    movl    %ebx, %esi
    movl    $.LC3, %edi
    movl    $1, %eax
    addq    $1, %rbx
    movapd  %xmm4, %xmm0
    movsd   %xmm4, 8(%rsp)
    call    printf
    cmpq    $10000, %rbx
    jne .L23
    addq    $240016, %rsp
    .cfi_def_cfa_offset 16
    xorl    %eax, %eax
    popq    %rbx
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE4:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE4:
    .section    .text.startup
.LHOTE4:
    .section    .rodata.cst16,"aM",@progbits,16
    .align 16
.LC1:
    .long   0
    .long   1
    .long   2
    .long   3
    .align 16
.LC2:
    .long   4
    .long   4
    .long   4
    .long   4
    .ident  "GCC: (Debian 4.9.1-16) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

您能否向我解释一下与 C 代码相关的上述汇编代码的主要步骤,特别是“测试”函数、主函数中的初始化循环和参数传递(即 push 和 pop 指令在哪里?对于堆栈)以及“a”和“b”数组的有效添加?

.L2, .L3, ... 段对应的是什么? L2缓存、L3缓存有关系吗?

很抱歉这些基础问题,但我从 Intel x86_64 汇编器开始。

感谢您的宝贵帮助

【问题讨论】:

  • 有两件事会让你在理解正在发生的事情时遇到问题。首先,当您转储汇编代码时,如果有些语句似乎没有任何意义,您将需要尝试将优化减少到 -O1(或根本不优化)。重度优化虽然对编译器很好,但会产生一些汇编器输出,有时只有编写优化例程的人才能识别这些输出。接下来,如果您学习汇编已经有一段时间了(例如从 32 位迁移到 64 位),您会遇到系统调用和调用约定方面的差异。

标签: c assembly att


【解决方案1】:

生成的汇编代码相当复杂。它首先检查数组 a、b 和 c 是否以会导致优化循环失败的方式重叠。例如,如果您这样做:

test(tab1, tab2, &tab1[1]);

然后会检测到重叠并导致代码跳转到 L7(简单的实现)。顺便说一句,L代表Label,标签编号只是编译器生成的,没有特殊含义。所以 L1、L2、L3 等只是用于代码分支到各个地方的标签。重叠检查从.LFB3 开始,到最后一个je .L7 结束。

如果未检测到重叠,则将使用优化循环。这个优化的循环将尝试一次添加两个双打而不是一个。优化循环所做的第一件事是确定数组a 是否与16 字节边界对齐(@9​​87654325@ 指令)。如果是,它将跳转到 L8 以加载一组常量(例如 r9 = 5000)。如果数组未对齐,if 将通过并加载一组不同的常量(例如 r9 = 4999),并且还处理第一个元素。这是因为未对齐的情况需要一次进行两次 4999 次迭代,并在循环外分别处理第一个和最后一个未对齐的元素。对齐的情况只会进行 5000 次迭代。

无论哪种方式,代码接下来都会到达 L3。 L3 和 L4 处的代码是优化循环,使用 addpd 指令一次执行两个相加(L7 处的非优化循环使用 addsd 一次执行一个相加)。在 L4 循环完成后,它会检查是否需要处理最后一个元素(对于未对齐的情况)。然后它返回 ret 指令。

顺便说一下,知道当test被调用时,ardi中,brsi中,crdx中。这是 64 位的调用约定。因此,没有参数被压入堆栈。如果您不太了解 x86 汇编,请专注于从 L7 开始的代码。那是未优化的版本,鉴于我说您的三个参数在 rdi、rsi 和 rdx 中,您应该能够弄清楚那部分。

【讨论】:

    【解决方案2】:

    .L2 等是标签,用来指代下一条指令。如果您使用过goto,它们与C 中的标签非常相似。标签的主要用途是与跳转或分支一起使用,以指定跳转的位置。

    例如,.L2 标签是 test()for (i = 0; i &lt; SIZE; i++) 循环体的开始,它按 8 个字节(double 的大小)计数,最大为 8*10000。循环中的最后一条指令是jne .L2,如果之前的比较不相等则跳转到.L2

    您可能会发现 x64 上的 this reference (PDF) 很有帮助。

    【讨论】:

      猜你喜欢
      • 2015-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-04
      • 2011-01-20
      相关资源
      最近更新 更多