29
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

x86-64プロセッサでGNU assemblerを使う

はじめに

x86-64プロセッサでAssembly Codeを作成する方法を説明します。

Assembly CodeをMachine Codeに変換するプログラムをAssemblerといいます。
ここではGNU assembler(GAS)を使用します。

Assembly Codeには決まり事があります。
この決まり事はApplication binary interface(ABI) or Calling conventionと呼ばれており、
instruction set / OS / compilerによって変わります。

x86-64プロセッサの代表的なABIは次の通りです。
それぞれUnix like OS / Windowsに対応します。

  • System V AMD64 ABI
  • Microsoft x64 calling convention

ここではSystem V AMD64 ABIについて説明します。

cpu / os / gcc

使用したcpu / os / gccを明記します。

  • CPU Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
  • OS ubuntu-14.04-desktop-amd64
  • gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

basic information

data type, register, instructionを説明します。

  • data type
  • general purpose register
  • instructions

次の資料を参考にしました。

[1] System V Application Binary Interface AMD64 Architecture Processor Supplement Draft Version 0.3
[2]Computer Systems - A Programmers Perspective
[3]x86-64 Machine-Level Programming Randal E. Bryant David R. O’Hallaron September 9, 2005
[4]Notes on x86-64 programming

data type

x86-64 registers, memory and operationsは次に示すdata typesを使用します。
次の表はC declaration / Intel data type / GAS suffix / x86-64 Sizeの対応表です。

C declaration Intel data type GAS suffix x86-64 Size (Bytes)
char Byte b 1
short Word w 2
int Double word l 4
unsigned Double word l 4
long int Quad word q 8
unsigned long Quad word q 8
char * Quad word q 8
float Single precision s 4
double Double precision d 8
long double Extended precision t 16

Long / Pointerは64bitです。LP64として知られています。
なお、Microsoft x64 calling convention は
Long Long / Pointerが64bitです。LLP64として知られています。

GAS suffixとはGNU assemblerのニーモニックの後ろに付く文字です。
mov命令を例にすると、
movbはオペランドが1byteであることを、
movqはオペランドが8byteであることを示します。

なお、GAS suffixは省略できます。具体的には
movl %eax, %ebx と記載しなくても、単にmov %eax, %ebxでよいです。

general purpose register

16個のgeneral purpose registers(GPRs)が利用できます。サイズは64bit長です。

63                        31            15      7      0 bit
 -------------------------------------------------------
 | rax                     |  eax     ax |  ah  |   al |
 -------------------------------------------------------
 | rbx                     |  ebx     bx |  bh  |   bl |
 -------------------------------------------------------
 | rcx                     |  ecx     cx |  ch  |   cl |
 -------------------------------------------------------
 | rdx                     |  edx     dx |  dh  |   dl |
 -------------------------------------------------------
 | rsi                     |  esi     si |      |  sil |
 -------------------------------------------------------
 | rdi                     |  edi     di |      |  dil |
 -------------------------------------------------------
 | rbp                     |  ebp     bp |      |  bpl |
 -------------------------------------------------------
 | rsp                     |  esp     sp |      |  spl |
 -------------------------------------------------------
 |  r8                     |  r8d    r8w |      |  r8b |
 -------------------------------------------------------
 |  r9                     |  r9d    r9w |      |  r9b |
 -------------------------------------------------------
 | r10                     | r10d   r10w |      | r10b |
 -------------------------------------------------------
 | r11                     | r11d   r11w |      | r11b |
 -------------------------------------------------------
 | r12                     | r12d   r12w |      | r12b |
 -------------------------------------------------------
 | r13                     | r13d   r13w |      | r13b |
 -------------------------------------------------------
 | r14                     | r14d   r14w |      | r14b |
 -------------------------------------------------------
 | r15                     | r15d   r15w |      | r15b |
 -------------------------------------------------------

同じregisterを32bit / 16bit / 8bit長でアクセスできます。
raxを例に説明します。名称と対応するbitは以下の通りです。
raxは64bit
eaxは下位32bit
axは下位16bit
ahは下位16bitの上位8bit
alは下位16bitの下位8bit

各registerは次に示すように用途が決められています。

  • Return value : rax
  • Argument : rdi(1st) rsi(2nd) rdx(3rd) rcx(4th) r8(5th) r9(6th)
  • Callee saved : rbx rbp r13-15
  • Stack pointer : rsp

(*)資料[1]には
r10はtemporary register, used for passing a function’s static chain pointer
r11はtemporary register
r12はcallee-saved register
と記載されています。

Callee saved registerは呼出し先で変更できないregisterです。
呼出し元の視点ではcall命令の前後で値が変わらないことが保証されます。

呼出し先でCallee saved registerを利用する場合は
registerの値をmemoryに保存・復元する必要があります。

次のregisterは関数の引数に利用されます。

Arg # Size (bits) > > >
64 32 16 8
1 %rdi %edi %di %dil
2 %rsi %esi %si %sil
3 %rdx %edx %dx %dl
4 %rcx %ecx %cx %cl
5 %r8 %r8d %r8w %r8b
6 %r9 %r9d %r9w %r9b

必要に応じてcall命令の前にregisterに値を設定します。

例えばadd(int a, int b)という関数をadd(10, 20)のように呼び出す場合、
次のAssembly Codeになります。%esi(1st)に10を%edi(2nd)に20を設定してcallします。

test.s
    .global main
format:
    .asciz "%d\n"
main:
    mov $10, %esi
    mov $20, %edi
    call add
    mov $format, %rdi
    mov %eax, %esi
    xor %rax, %rax
    call printf
    ret
add:
    xor %rax, %rax
    add %edi, %eax
    add %esi, %eax
    ret

instructions

x86-64 Instruction setを説明します。
以降の説明で用いる用語を説明します。

  • Iは直値です。
  • Rはregisterです。
  • Sは直値 or register or memoryです。
  • Dはregister or memoryです。
  • Mはmemoryです。
  • (*1)のInstructionには後ろに[b|w|l|q]が付きます。
  • (*2)のInstructionには後ろに[bw|bl|bq|wl|wq|lq]が付きます。

(*1)の例としてmov(*1)の場合、
movb / movw / movl / movqとなります。b,w,l,qはGAS suffixです。

(*2)の例としてmovs(*2)の場合、
movsbw / movsbl / movsbq / movswl / movswq / movslqとなります。
bwの意味は b → wの変換をするという意味です。bl/bq/wl/wq/lqも同様の意味です。

Data movement

register / memory間でデータをコピーする命令を説明します。
Instructionの一覧を示します。

Instruction Effect Description
mov(*1) S, D S → D Move
movabsq I, R I → R Move quad word
movs(*2) S, R SignExtend(S) → R Move sign-extended
movz(*2) S, R ZeroExtend(S) → R Move zero-extended
pushq S R[%rsp]-8 → R[%rsp];
S → M[R[%rsp]]
Push
popq D M[R[%rsp]] → D;
R[%rsp]+8 → R[%rsp]
Pop

movは同一サイズのデータをコピーする命令です。DにSをコピーします。
movabsqは64bitの直値を64bit registerにコピーします。
movsはsign extension, movzはzero extensionします。詳細は後で説明します。
pushq / popqはstackに8byteデータをpush / popします。

(*)movzlq命令はありません。理由は[3]に次のように記載されています。

Perhaps unexpectedly, instructions that move or generate
32-bit register values also set the upper 32 bits of the register to zero.
Consequently there is no need for an instruction movzlq.

sign extension / zero extension

データをサイズの大きい領域にコピーする場合、次の2つの方式があります。

  • sign extension
  • zero extension

sign extensionは

  • コピー元の最上位ビットが1であればコピー先の上位ビットに1を埋める、
  • コピー元の最上位ビットが0であればコピー先の上位ビットに0を埋める、

方式です。

zero extensionはコピー先の上位ビットに0を埋める方式です。

sign extensionでは符号が変化しません。
zero extensionでは符号が変化します。
扱う整数が符号付きかどうかで使い分けます。

例.1byte を 2byteに代入するケース

case 1: 0010 1101 (signed +0x2d, unsigned 0x2d)
 0010 1101 b -> 0000 0000 0010 1101 b (sign ext; +0x002d)
 0010 1101 b -> 0000 0000 0010 1101 b (zero ext;  0x002d)

case 2: 1010 1101 (signed -0x53, unsigned 0xad)
 1010 1101 b -> 1111 1111 1010 1101 b (sign ext; -0x0053)
 1010 1101 b -> 0000 0000 1010 1101 b (zero ext;  0x00ad)

Addressing Modes

mov命令はmemoryを参照できます。memory addressは次の形式で参照します。
effective addressといいます。

offset(base,index,scale)

effective addressは(base + index * scale + offset)で計算します。

offsetは固定値 or labelです。base / indexはregisterです。
scaleは1 or 2 or 4 or 8です。

例. %rsp

# %rspに設定されたaddressにあるwordを%diにコピーします。
# offset / index / scaleは省略されています。
mov (%rsp), %di

# mov %rsp, %di
# ()なしにすると%rspの内容をコピーすることになります。誤りです。

PC-relative addressing

x86-64でコンパイルした実行ファイルをobjdumpでdisassembleすると
%ripからの相対アドレスで表現している命令が多くあることに気づきます。
%ripを使ったAddressingをPC-relative addressingといいます。

例.

$ objdump -d test | grep rip
  4003e4:   48 8b 05 0d 0c 20 00    mov    0x200c0d(%rip),%rax        # 600ff8 <_DYNAMIC+0x1d0>
  400400:   ff 35 02 0c 20 00       pushq  0x200c02(%rip)        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
(...)
$ 

この形式が使われる理由は次の通りです。

Offsets are limited to 32 bits. This means that only a 4GB window into the
potential 64-bit address space can be accessed from a given base value. This is
mainly an issue when accessing static global data. It is standard to access this
data using PC-relative addressing (using %rip as the base). For example,
we would write the address of a global value stored at location labeled a as
a(%rip), meaning that the assembler and linker should cooperate to compute the
offset of a from the ultimate location of the current instruction.

Integer arithmetic & logic operations

Integerのarithmetic & logic operationsを説明します。

  • lea
  • arithmetic : inc / dec / add / sub
  • logic : neg / not / xor / or / and
  • shift : sal / sar / shl / shr

次のInstructionはすべて(*1)となります。

Instruction Effect Description
lea S, D &S → D Load effective address
inc D D+1 → D Increment
dec D D-1 → D Decrement
add S, D D+S → D Add
sub S, D D-S → D Subtract
neg D -D → D Negate
not D ~D →D Complement
xor S, D D^S → D Exclusive-or
or S, D D|S → D Or
and S, D D&S → D And
sal k, D D<<k → D Left shift
shl k, D D<<k → D Left shift (same as sal)
sar k, D D>>k → D Arithmetic right shift
shr k, D D>>k → D Logical right shift

lea / sarについて補足説明します。
lea はeffective addressをDに設定します。

例. lea と movの違い
movは%ripが指し示すaddressの先の8byteを%rsiにコピーします。
leaは%ripが指し示すaddressを%rsiにコピーします。

lea (%rip), %rsi # 0x400542
mov (%rip), %rsi # 0xfffffebfe8c03148

sarは最上位ビットを保持したまま右bit shiftします。
つまり
最上位ビットが1であればシフトしてできる左側には1を詰めます。
最上位ビットが0であればシフトしてできる左側には0を詰めます。

例. sar / shrの違い

mov $0x80, %sil
sar $7, %sil # %sil = 0xFF
mov $0x80, %sil
shr $7, %sil # %sil = 0x01

Multiplication and division operations

Instruction Effect Description
imulq S S × R[%rax]→R[%rdx]:R[%rax] Signed full multiply
mulq S S × R[%rax]→R[%rdx]:R[%rax] Unsigned full multiply
cltq SignExtend(R[%eax])→R[%rax] Convert %eax to quad word
cqto SignExtend(R[%rax])→R[%rdx]:R[%rax] Convert to oct word
idivq S R[%rdx]:R[%rax] mod S→R[%rdx]
R[%rdx]:R[%rax]÷S→R[%rax]
Signed divide
divq S R[%rdx]:R[%rax] mod S→R[%rdx]
R[%rdx]:R[%rax]÷S→R[%rax]
Unsigned divide
  • sample
test.s
    .global main
format:
    .asciz "%016lx %016lx\n"
main:
    movabs $0xFFFFFFFFFFFFFFFF, %rax
    movabs $0xFFFFFFFFFFFFFFFF, %r10
    mul %r10 # %rdx:%rax
    mov $format, %rdi
    mov %rdx, %rsi
    mov %rax, %rdx
    xor %rax, %rax
    call printf
    ret

FLAGS register

CPUは直前に実行したinstructionに応じて状態をFLAGS registerに保持します。
FLAGS registerを使って処理を分岐させます。

pushf / popf instructionでFLAGS registerをstackにpush / popできます。
pushした値をmovでreadすることで FLAGS registerを読み込めます。

FLAGS registerのbit assignは次の通りです。

  15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0 
 -------------------------------------------------
 |  |  |  |  |OF|DF|IF|TF|SF|ZF| 0|AF| 0|PF| 1|CF|
 -------------------------------------------------

CF:Carry Flag
PF:Parity Flag
AF:Adjust Flag
ZF:Zero Flag
SF:Sign Flag
TF:Trap Flag
IF:Interrupt Enable Flag
DF:Direction Flag
OF:Overflow Flag

read方法は次の通りです。

    .global main
format:
    .asciz "0x%04x\n"
main:
    xor %rax, %rax
    pushf
    mov (%rsp), %ax
    popf
    mov %eax, %esi
    mov $format, %rdi
    xor %rax, %rax
    call printf
    ret

FLAGS registerをprintします。

test.s
    .global main
format:
    .asciz "ZF=%01x SF=%01x CF=%01x OF=%01x\n"
print_flags:
    xor %r13, %r13
    mov %di, %r13w
    mov $format, %rdi # 1st
    xor %rsi, %rsi    # 2nd ZF
    bt  $6, %r13w
    lahf
    mov %ah, %al
    mov %al, %sil
    and $0x01, %sil
    xor %rdx, %rdx    # 3rd SF
    bt  $7, %r13w
    lahf
    mov %ah, %al
    mov %al, %dl
    and $0x01, %dl
    xor %rcx, %rcx    # 4th CF
    mov %r13b, %cxl
    and $0x01, %cxl
    xor %r8,  %r8     # 5th OF
    bt  $11, %r13w
    lahf
    mov %ah, %al
    mov %al, %r8b
    and $0x01, %r8b
    xor %rax, %rax
    call printf
    ret
main:
    xor %rdi, %rdi  # %rdi=0
    mov $0x7f, %bl
    add $0x00, %bl
    pushf
    mov (%rsp), %di # %di=FLAGS register
    popf
    call print_flags
    ret

ZF:Zero Flag

0であれば1、そうでなければ0になります。

例.

mov $0x00 %bl
test %bl, %bl # ZF = 1
mov $0x01 %bl
test %bl, %bl # ZF = 0

SF:Sign Flag

負であれば1、そうでなければ0になります。
結果の最上位ビットを返します。負であれば最上位ビットは1です。

例.

mov $0x80 %bl
test %bl, %bl # SF = 1
mov $0x7f %bl
test %bl, %bl # SF = 0
mov $0x00 %bl
test %bl, %bl # SF = 0

CF:Carry Flag

符号なし演算としてcarry or a borrowが発生すれば1、そうでなければ0になります。

例. 符号なし 8bit range(0~255)

mov $0xff %bl
add $0x01 %bl # CF = 1, 255+1=256
mov $0xfe %bl
add $0x01 %bl # CF = 0, 254+1=255
mov $0x01 %bl
sub $0x01 %bl # CF = 0, 1-1=0
mov $0x01 %bl
sub $0x02 %bl # CF = 1, 1-2=-1

OF:Overflow Flag

符号付き演算としてcarry or a borrowが発生すれば1、そうでなければ0になります。

例. 符号付き 8bit range(-128~0~127), 0xff(-1)~0x80(-128)~0x7f(127)~0x00(0)

mov $0x7f %bl
add $0x01 %bl # OF = 1, 127+1=128
mov $0x7e %bl
add $0x01 %bl # OF = 0, 126+1=127
mov $0x80 %bl
sub $0x01 %bl # OF = 1, -128-1=-129
mov $0x81 %bl
sub $0x01 %bl # OF = 0, -127-1=-128

Condition Codes

FLAGS registerの組み合わせに応じてCondition Codes(cc)が決まります。
Condition Codesに対応したjump命令があります。

cmp / test命令を利用するとregisterを変更することなしにFLAGS registerを更新できます。

  • cmp s2,s1 : set flags based on s1 - s2
  • test s2,s1 : set flags based on s1&s2 (logical and)

cmp実行後のFLAGS registerとccの対応を次に示します。
ccに応じてjump命令が決まります。
フォーマットはjccとなります。例. cc=eであればjeとなります。

cc condition tested meaning after cmp
e ZF equal to zero
ne ˜ ZF not equal to zero
s SF negative
ns ˜ SF non-negative
g ˜ (SF xor OF) & ˜ ZF greater (< signed)
ge ˜ (SF xor OF) greater or equal (<= signed)
l SF xor OF less (signed <)
le (SF xor OF) | ZF less or equal (signed <=)
a ˜ CF & ˜ ZF above (< unsigned)
ae ˜ CF above or equal (<= unsigned)
b CF below (< unsigned)
be CF | ZF below or equal (<= unsigned)

sample code

printf

レジスタの値をprintfするだけのsimpleなAssembly Codeを示します。
esiをprintfします。

test.s
    .global main
main:
    mov  $format, %rdi # 1st parameter
    mov  $10,  %rsi    # 2nd parameter
    xor  %rax, %rax    #
    call printf        # printf("%d\n", %rsi)
    ret
format:
    .asciz "%d\n"
gcc -O2 test.s -o test && ./test
10

rdtscp

rdtscp命令は次の2つの情報を読み出すことができます。

  • Time-Stamp Counter
  • Processor ID

命令実行後に次のレジスタが更新されます。

  • edx:eax : 64bit Time-Stamp Counter
  • ecx : Processor ID

Time-Stamp CounterはCPUのclockに合わせてインクリメントされるカウンタ値です。

sample codeを示します。rdtscpを連続して実行します。
1度目のrdtscp後にEAXを退避してすぐに2度目のrdtscpを実行します。
1度目と2度目の差分のカウンタ値をprintfします。

ばらつきがありますが最小で77 clockかかりました。

test.s
    .global main
main:
    movl $10, %r13d
loop:
    rdtscp
    mov  %eax, %r8d
    rdtscp
    mov  %eax, %r9d
    sub  %r8d, %r9d
    mov  $format, %rdi # 1st parameter
    mov  %r9d,  %esi   # 2nd parameter
    mov  %ecx,  %edx   # 3rd parameter
    xor  %rax, %rax    #-
    call printf        # printf
    decl %r13d
    jne  loop
    ret
format:
    .asciz "%03d %d\n"
console
$ ./test
088 1
077 1
088 1
099 1
088 1
...
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
29
Help us understand the problem. What are the problem?