Your Satisfaction Is Our Success

Hướng dẫn Assembly 64bit - Bài 2.

Tiếp theo bài trước, sau khi các bác đã có 1 chương trình hoàn chỉnh hiển thị dòng chữ “Hello World!”, bài này sẽ hướng dẫn các bạn viết một hàm hoàn chỉnh trong assembly nhằm mục đích mô-đun hoá ứng dụng. Để làm được điều đó, các bạn cần biết 1 số khái niệm và mình xin phép đi lần lượt trong bài hôm nay.

1. Lời gọi hàm:

Trong bài trước mình đã có đoạn mã để hiển thị dòng chữ Hello World! Nếu như mình muốn sử dụng lại đoạn mã này ở các đoạn chương trình khác, thay vì gõ lại đoạn mã, mình viết riêng đoạn mã này thành một module và gọi khi cần thiết.

_print:
  movl $0x2000004, %eax
  movq helloMessage@GOTPCREL(%rip), %rsi
  movl $1, %edi
  movq $100, %rdx
  syscall
  retq  /// thoát khỏi hàm

_exit:
  movl $0x2000001, %eax
  movl $0, %ebx
  syscall
  retq

_main:
  callq _print
  callq _exit

Chỉ thị callq sẽ đưa con trỏ của chương trình tới nhãn _print và thực hiện các câu lệnh ở đó. Sau đó khi gặp chỉ thị retq, chương trình sẽ quay lại tại thời điển callq. Thật là vi diệu. Và cũng khá là dễ hiểu.

Bây giờ mình hoàn toàn có thể gọi hàm print bất cứ lúc nào để in dòng chữ “Hello World!”! Thật là chán quá đi mất. Làm thế nào để in các dòng chữ khác?

Chúng ta hãy nghĩ tới việc truyền tham số vào hàm. Tương tự như syscall, các tham số được truyền vào các thanh ghi. Đối với hàm cũng tương tự:

_print:
  movl $0x2000004, %eax
  movl $1, %edi
  syscall
  retq  /// thoát khỏi hàm

// gọi hàm _print
  movq helloMessage@GOTPCREL(%rip), %rsi
  movq $100, %rdx
  callq _print

Với calling convention của MacOS, các tham số sẽ lần lượt được chuyển vào các thanh ghi theo thứ tự sau: rdi, rsi, rdx, rcx, r8d, r9d... khi theo đúng convention này thì hàm các bạn viết có thể được sử dụng trong C. giá trị trả về thường được ghi vào thanh ghi ax.

Ví dụ hàm cộng hai số:

_add:
  movl %esi, %eax
  addl %edi, %eax
  retq

Sẽ tương đương với hàm:

int add(int a, int b) {
  return a + b;
}

Nhưng hàm add trên, khi biên dịch ra mã máy bằng gcc sẽ có kết quả như sau (các đoạn mã sẽ được giải thích phần sau)

_add:                                   ## @add
  pushq	%rbp
  movq	%rsp, %rbp
  movl	%edi, -4(%rbp)
  movl	%esi, -8(%rbp)
  movl	-4(%rbp), %esi
  addl	-8(%rbp), %esi
  movl	%esi, %eax
  popq	%rbp
  retq

Rất dài và tất nhiên không tốt bằng việc bạn viết bằng assembly rồi, đúng không nhỉ. Bây giờ chúng ta sẽ thử gọi hàm _printf để in ra dòng chữ “Hello World!” thay vì dùng hàm hệ thống syscall.

printf(format, params...) => prinf("Hello World" (%rdi))
=> 
movq helloMessage@GOTPCREL(%rip), %rdi // rdi (64 bit) - edi (32 bit)
callq _printf

Ta thử viết một chương trình hoàn chỉnh như sau:

.section __DATA,__data
    helloMessage: .asciz "Result: %d!
"
.section __TEXT,__text
.globl _main
.globl _add

_add: // int add(int a, int b)
  movl %esi, %eax
  addl %edi, %eax
  ret

_main:
  pushq %rbp // 
  movl $2, %esi // eax = add(2, 3)
  movl $3, %edi
  callq _add

  movq helloMessage@GOTPCREL(%rip), %rdi // printf("Result: %d", 5)
  movl %eax, %esi
  callq _printf
  
  movl $0, %edi // exit (0)
  callq _exit

[Đang viết tiếp...]

Style Switcher

Predifined Colors


Footer


Layout Mode

Patterns