AT&T 汇编-8: 内存变址寻址

# array.s

.section .rodata
msg:
   .asciz   "The value is: %d.\n"
array:
   .int 5, 10, 15, 20, 25

.section .text
.globl main
main:
   movl  $0,   %edi

loop1:
   pushl array(, %edi, 4)
   pushl $msg
   call  printf
   addl  $8,   %esp
   inc   %edi
   cmp   $5,   %edi
   jne   loop1

   pushl $0
   call  exit

第 7 行声明了 5 个整数,它们在内存里的位置是连续的,通过起始位置 array 可以顺序访问每一个值。第 12 行把寄存器 edi 初始化为 0。edi 和 esi 也是通用寄存器,它们经常成对出现,多用于字符串操作,esi 表示源字符串的地址,edi 表示目的地址。

循环中依次打印每个数组元素的值。第 15 行采用了一个访问连续相同大小元素的方法:

base_address (offset, index, size)

得到要访问的元素的起始地址为

base_address + offset + index * size

括号内的三个元素中如果某个值为 0 可以不写。第一次执行第 15 行的语句时,因为 edi 的值为 0,得到的内存位置是 array + 0 * 4 = array,即第一个元素的值,以后随着 edi 的递增依次访问后面的元素。

第 19 行把 edi 的值加 1。对某个值进行递增递减是常见的操作,AT&T 汇编中为此提供了单独的 inc 指令,另外还有 dec 指令用于递减的操作,有点类似于 c 语言中的“++”和“–”操作符。另外 inc 和 dec 不会影响进位标志(CF),这个不同于使用 add 和 sub 指令。

上面这段程序有点像下面的 c 语言程序:

#include <stdio.h>

int main()
{
   register int i;
   int array[] = {5, 10, 15, 20, 25};

   for (i = 0; i < 5; ++i)
      printf("The value is: %d.\n", array[i]);

   return 0;
}

另外一种方法可以使用寄存器间接寻址,也就是 c 语言中的“指针”来访问。

# ptr.s

.section .rodata
array:
   .int  5, 10, 15, 20, 25
msg:
   .asciz   "The value is: %d.\n"

.section .text
.globl main
main:
   movl  $array,  %esi
   movl  $msg,    %edi # end position

loop1:
   pushl (%esi)
   pushl $msg
   call  printf
   addl  $8,   %esp

   addl  $4,   %esi
   cmp   %esi, %edi
   jne   loop1

   pushl $0
   call  exit

第 12 行把 array 的起始地址保存到 esi 中。第 16
行采用了寄存器间接寻址的方法:esi 中保存的是 array 的地址,“(%esi)”表示取 esi 中的地址所在的内容。第 21 行把地址递增 4,指向下一个元素。这类似于下面的 c 程序:

#include <stdio.h>

int main()
{
   int array[] = {5, 10, 15, 20, 25};
   register int *ptr, *end;

   for (ptr = array, end = array + 5; ptr != end; ++ptr)
      printf("The value is: %d.\n", *ptr);

   return 0;
}

如果元素的大小不相同,要访问当前地址的某个偏移量的值,可以采用基址+偏移量的方法。在 AT&T 的语法中不允许把值与寄存器直接相加,而要把值放在括号外。

# offset.s

.section .rodata
int64:
   .quad 120
int32:
   .int  5
msg:
   .asciz   "The second value is: %d.\n"

.section .text
.globl main
main:
   movl  $int64,  %eax
   pushl 8(%eax)
   pushl $msg
   call  printf
   addl  $8,   %esp

   pushl $0
   call  exit

第 4,5 行声明了一个 64 位的整数 int64,接着是一个 32 位的整数 int32。第 14 行把 int64 的地址放到 eax 中,接着第 15 行的“8(%eax)”表示 eax 保存的地址之后的 8 个字节的位置,也就是 int32 的起始地址。最后打印出 int32 的值。如果想要定位当前地址之前的位置只需把括号左边的正数换成负数即可。

发表于 2011年6月8日
  1. william
    2012年8月16日 21:31 | #1

    offset.s 是否可以变成这样:

    #offset.s
    .section .rodata
    int32:
    .int 5
    msg:
    .asciz “The second value is: %d.\n”
    .section .text
    .globl main
    main:
    movl $int32, %eax
    pushl (%eax)
    pushl $msg
    call printf
    addl $8, %esp

    pushl $0
    call exit

    这样就相当于去除了int64这个变量。因为看不出int64有什么作用。更不知道
    .quad是什么意思,能解释下吗?

    • ou
      2012年8月16日 21:47 | #2

      .quad表示64位整数,.int是32位整数。文件名叫offset.s,”int64″只是为了演示“基址+偏移量”这个语法而加上的一个偏移量而已,或者”int64″改名叫”offset64″比较贴切 :)

      • william
        2012年8月17日 09:01 | #3

        明白了,谢谢!

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>