Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1176418
  • 博文数量: 573
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 66
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-28 16:21
文章分类

全部博文(573)

文章存档

2018年(3)

2016年(48)

2015年(522)

分类: C/C++

2015-12-09 19:21:45

gcc, as, ld的一些笔记(2) 

9.gas汇编工具:asat&t风格)语法说明

使用$标识立即数

再寄存器前面加上%

源操作数在前,目标操作数在后

使用$获取变量地址

长跳转使用:ljmp $section, $offset

一个简单的汇编语言程序框架:

.section .data

              ……

.section .bss

              ……

.section .text

.globl _start

_start:

       ……

 

范例:

#cpuid2.s View the CPUID Vendor ID string using C library calls

.section .datatext

output:

    .asciz "The processor Vendor ID is '%s'\n"

.section .bss

    .lcomm buffer, 12

.section .text

.globl _start

_start:

    movl $0, %eax

    cpuid

    movl $buffer, %edi

    movl %ebx, (%edi)

    movl %edx, 4(%edi)

    movl %ecx, 8(%edi)

    pushl $buffer

    pushl $output

    call printf

    addl $8, %esp

    pushl $0

call exit

 

伪指令说明:

data

.ascii

定义字符串,没有\0结束标记

data

.asciz

\0结束标记

data

.byte

字节

data

.int

32

data

.long

32

data

.shot

16

bss

.lcomm

对于上面的例子是声明12字节的缓冲区,l标识local,仅当前汇编程序可用

bss

.comm

通用内存区域

data/text

.equ

.equ  LINUX_SYS_CALL, 0x80

movl $ LINUX_SYS_CALL, %eax

说明:equ不是宏而是常量,会占据数据/代码段空间

 

指令集说明:

 

movb/movw/movl

 

 

cmov

根据cf, of, pf, zf等标识位判断并mov

 

xchg

操作时会lock内存,非常耗费cpu时间

 

bswap

翻转寄存器中字节序

 

xadd

 

 

pushx, popx

 

 

pushad, popad

 

 

jmp

 

 

call

 

 

cmp

 

 

jz/jb/jne/jge

 

 

loop

 

 

addb/addw/addl

 

 

subb/subw/subl

 

 

dec/inc

 

 

mulb/muw/mull

无符号乘法

源操作数长度

目标操作数

目标位置

8

al

ax

16

ax

dx:ax

32

eax

edx:eax

 

imul有符合乘法

 

 

divb/divw/divl

无符合除法

(被除数在eax中,除数在指令中给出)

被除数

被除数长

余数

ax

16

al

ah

dx:ax

32

ax

dx

edx:eax

64

eax

edx

 

idiv有符合除法

 

 

sal/shl/sar/shr

移位

 

rol/ror/rcl/rcr

循环移位

 

leal

取地址:leal  output, %eax

等同于:movl  $output, %eax

 

rep

rep movsb      执行ecx

 

lodsb/lodsw/lodsl

stosb/stosw/stosl

取存内存中的数据









 

gas程序范例(函数调用):

文件1area.s定义函数area

# area.s - The areacircumference function

.section .text

.type area, @function

.globl area

area:

   pushl %ebp

   movl %esp, %ebp

   subl $4, %esp

   fldpi

   filds 8(%ebp)

   fmul %st(0), %st(0)

   fmulp %st(0), %st(1)

   fstps -4(%ebp)

   movl -4(%ebp), %eax

   movl %ebp, %esp

   popl %ebp

   ret

 

文件2functest4.s调用者

# functest4.s - An example of using external functions

.section .data

precision:

   .byte 0x7f, 0x00

.section .bss

   .lcomm result, 4

.section .text

.globl _start

_start:

   nop

   finit

   fldcw precision

 

   pushl $10

   call area

   addl $4, %esp

   movl %eax, result

 

   pushl $2

   call area

   addl $4, %esp

   movl %eax, result

 

   pushl $120

   call area

   addl $4, %esp

   movl %eax, result

 

   movl $1, %eax

   movl $0, %ebx

   int $0x80

 

10.读连接器和加载器的一些笔记,感谢原作者colyli at gmail dot com,看了他翻译的lnl及写的一个os,受益匪浅。

如果不是很深入的研究连接器和加载器的话,了解一些原理就足够了。举个例子说明吧:

  1 #include

  2 #include

  3 #include

  4 #include

  5

  6 int a = 1;

  7 int main()

  8 {      

  9         printf("value: %d\n", a);

 10        

 11         return 0;

 12 }

编译指令:gcc -c hello.c -o hello.o                   汇编

gcc -o hello hello.o                          编译

objdump -d hello.o                          反汇编目标文件

objdump -d hello                             反汇编可执行文件

比较两端结果:

objdump -d hello.o

objdump -d hello

00000000

:

   0:   55         push  %ebp

   1:   89 e5       mov  %esp,%ebp

   3:   83 ec 08    sub   $0x8,%esp

   6:   83 e4 f0     and  $0xfffffff0,%esp

   9:   b8 00 00 00 00  mov    $0x0,%eax

   e:   83 c0 0f        add    $0xf,%eax

  11:   83 c0 0f        add    $0xf,%eax

  14:   c1 e8 04        shr    $0x4,%eax

  17:   c1 e0 04        shl   $0x4,%eax

  1a:   29 c4          sub   %eax,%esp

  1c:   83 ec 08        sub  $0x8,%esp

  1f:   ff 35 00 00 00 00  pushl  0x0

  25:   68 00 00 00 00   push   $0x0

  2a:   e8 fc ff ff ff call  2b

  2f:   83 c4 10        add $0x10,%esp

  32:   b8 00 00 00 00  mov  $0x0,%eax

  37:   c9            leave 

  38:   c3            ret   

08048368

:

 8048368: 55      push   %ebp

 8048369: 89 e5    mov    %esp,%ebp

 804836b: 83 ec 08  sub    $0x8,%esp

 804836e: 83 e4 f0  and    $0xfffffff0,%esp

 8048371: b8 00 00 00 00  mov  $0x0,%eax

 8048376: 83 c0 0f        add  $0xf,%eax

 8048379: 83 c0 0f       add   $0xf,%eax

 804837c: c1 e8 04       shr   $0x4,%eax

 804837f:  c1 e0 04      shl   $0x4,%eax

 8048382: 29 c4          sub  %eax,%esp

 8048384: 83 ec 08       sub   $0x8,%esp

 8048387: ff 35 94 95 04 08 pushl 0x8049594

 804838d: 68 84 84 04 08  push $0x8048484

 8048392:   e8 19 ff ff ff   call  80482b0

                                           

 8048397: 83 c4 10       add  $0x10,%esp

 804839a:  b8 00 00 00 00 mov $0x0,%eax

 804839f: c9            leave 

 80483a0: c3            ret   

 80483a1: 90           nop   

 80483a2: 90           nop   

 80483a3: 90           nop   

简单说明:由于程序运行时访问内存,执行跳转都需要确切的地址。所以汇编处理的目标文件里面没有包含,而是把这个工作放到连接器中:即定位地址。

当程序需要动态链接到某个库上时,使用该库的got表动态定位跳转即可。

具体可以看colyli大侠的《链接器和加载器Beta 2》,及《从程序员角度看ELF

 

11.连接器脚本ld—script(相关内容来自《GLD中文手册》)

ld --verbose查看默认链接脚本

ld把一定量的目标文件跟档案文件连接起来,并重定位它们的数据,连接符号引用.一般在编译一个程序时,最后一步就是运行ld

实例1

SECTIONS
{
      . = 0x10000;
      .text : { *(.text) }
      . = 0x8000000;
      .data : { *(.data) }
      .bss : { *(.bss) }
}

              注释:“.”是定位计数器,设置当前节的地址。

 

实例2

floating_point = 0;
    SECTIONS
    {

. = ALIGN(4);
      .text :
        {
          *(.text)
           _etext = .;

PROVIDE(etext = .);
    }

 

. = ALIGN(4);
      _bdata = (. + 3) & ~ 3;
      .data : { *(.data) }
    }

注释:定义一个符合_etext,地址为.text结束的地方,注意源程序中不能在此定义该符合,否则链接器会提示重定义,而是应该象下面这样使用:

extern char _etext;

但是可以在源程序中使用etext符合,连接器不导出它到目标文件。

 

实例3

  SECTIONS {
      outputa 0x10000 :
        {
        all.o
        foo.o (.input1)
        }
      outputb :
        {
        foo.o (.input2)
        foo1.o (.input1)
        }
      outputc :
        {
        *(.input1)
        *(.input2)
        }
  }

这个例子是一个完整的连接脚本。它告诉连接器去读取文件all.o中的所有节,并把它们放到输出节outputa的开始位置处, 该输出节是从位置0x10000处开始的。从文件foo.o中来的所有节.input1在同一个输出节中紧密排列。 从文件foo.o中来的所有节.input2全部放入到输出节outputb中,后面跟上从foo1.o中来的节.input1。来自所有文件的所有余下的.input1.input2节被写入到输出节outputc中。

 

示例4:连接器填充法则:

   SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }                    错误

    SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }          正确

 

示例5VMALMA不同的情况

    SECTIONS
      {
      .text 0x1000 : { *(.text) _etext = . ; }
      .mdata 0x2000 :
        AT ( ADDR (.text) + SIZEOF (.text) )
        { _data = . ; *(.data); _edata = . ;  }
      .bss 0x3000 :
        { _bstart = . ;  *(.bss) *(COMMON) ; _bend = . ;}
    }

程序:

    extern char _etext, _data, _edata, _bstart, _bend;
    char *src = &_etext;
    char *dst = &_data;

 

    /* ROM has data at end of text; copy it. */
    while (dst < &_edata) {
      *dst++ = *src++;
    }

 

    /* Zero bss */
    for (dst = &_bstart; dst< &_bend; dst++)
      *dst = 0;

示例6linux-2.6.14/arch/i386/kernel $ vi vmlinux.lds.S

linux内核的链接脚本,自行分析吧,有点复杂哦。


阅读(989) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~