Giới thiệu về lập trình Assembly trên Linux (AT&T Style không phải Intel Style)


Tham khảo
– Sách : AT&T Assembly Language, Richard Blum

1. Ngôn ngữ Assembly là gì?

  • Ở mức thấp nhất, Process chỉ hiểu instruction code
  • Instruction Code là các mã nhị phân chứa các thành phần: Instruction prefix, Opcode, ModR/M, SIB, Displacement, Data Element.
  • Người ta hoàn toàn có thể viết chương trình bằng instruction code, nhưng nó sẽ cực kì khó nhọc, bởi ta chỉ thấy không gì khác ngoài các byte nối tiếp nhau.
  • Việc sử dụng các ngôn ngữ bậc cao giúp việc viết chương trình dễ dàng hơn rất nhiều, vì trình biên dịch hoặc thông dịch đảm nhiệm việc chuyển mã ngôn ngữ bậc cao trực tiếp hoặc gián tiếp sang instruction code để chạy.
  • Tuy nhiên, việc sinh instruction code của trình biên dịch/thông dịch không phải luôn luôn hiệu quả.
  • Khi muốn động vào các kết quả được sinh ra từ trình biên dich/thông dịch ở trên, ta không khó có thể sử trực tiếp trên instruction code, một dạng dễ nhớ của các instruction code này được sinh ra để phục vụ việc này. Đó chính là Assembly Language.
  • Vì gần như được chuyển sang instruction code của Processor nên Assemble Code gần như được gắn với Processor nào đó.
  • Dù các lệnh của hầu hết bộ xử lý là tương tự nhau nhưng quy tắc gợi nhớ các mã Assembly dù chỉ khác nhau một chút thôi cũng khiến việc viết code cross-processor gần như là không thể rồi.
  • Ta nói Assembly Language là nói đến 1 ngôn ngữ, nhưng thực chất mỗi Processor có 1 Assembly Language riêng của nó.
  • Bởi vậy, để để lập trình với Assembly cho 1 Processor nào đó, ta cũng cần Assembler tương ứng cho Processor đó.

2. Intel ASM và AT&T ASM và ARM

  • Các bộ xử lý cùng kiến trúc thường ít có thay đổi về Assembly Language, tuy nhiên 2 kiến trúc khác nhau thì thường có Assembly Language khác nhau
  • ARM : Hiện tại chưa tìm hiểu được.
  • Nếu bỏ qua ARM, ta có thể chia ra làm 2 loại Assembly Language sau theo tiêu chí lệnh gợi nhớ, đó là IntelAT&T.
    Về sự khác nhau của 2 loại này:
    Hầu hết sự khác biệt xuất hiện ở một dài định dạng cụ thể. Nhưng những điểm khác nhau chính giữa IntelAT&T được đưa ra dưới đây:
  • AT&T sử dụng $ để kí hiệu, còn Intel thì không.
    Vì thế, khi biểu diễn giá trị 4, AT&T sẽ sử dụng $4, còn Intel thì
    đơn giản viết 4.

  • AT&T sử dụng thứ tự về source (nguồn), destination (đích) ngược với Intel,
    Ví thế, để chuyển giá trị 4 vào thanh ghi EAX, AT&T sẽ viết là
    movl $4, %eax, còn Intel lại viết là mov eax, 4.

  • Ở Long calls và jumps cũng sử dụng cú pháp khác nhau để định nghĩa segment và
    offset. AT&T sử dụng ljmp $section, $offset, trong khi đó Intel sử dụng
    jmp section:offset

3. ASM trên Linux

Để lập trình ASM trên Linux, ta sử dụng những công cụ sau:

  • Assembler :as
    Đây là GNU Assembler, là cross-platform assembler phổ biến nhất trên Linux.
    Như đã nói ở trên, mỗi assemble language thường gắn liền với assembler của nó. Vậy thì cross-platform ở trên là ý gì. Có nghĩa là compiler này có khả năng biên dịch nhiều loại assembly code khác nhau.
    Cùng 1 source nhưng nó có thể sinh ra nhiều loại instruction code tùy theo processor được chỉ đinh.

  • Linker :ld
    Ở ngôn ngữ C/C++ hầu như ít khi ta phải thao tác trực tiếp với linker. gcc thường sẽ làm tất rồi.
    Nhưng đối với assembler thì không như thế, nó sẽ không tự động gọi linker đâu, ta phải tự gọi linker để tạo ra file chạy cho source chúng ta viết.
    linker duy nhất sử dụng trên Linux chính là GNU Linker, hay ld.

  • Debugger : gdb
    Chúng ta đã có 1 loại bài về GDB rồi, gdb debug được mọi chương trình trong Linux dù chúng được viết bằng C/C++ hay bây giờ là Assembly đi nữa.

  • C/C++ Compiler :gcc
    Thực ra không trực tiếp liên quan lắm. Nhưng như trong 1 bài nói về Tìm hiểu thêm về biên dịch HelloWorl, ta cũng đề cập đến khả năng sinh Asssembly Code của gcc rồi.
    Có thể chúng ta sẽ sử dụng gcc để sinh mã ASM rồi optimize chúng thay vì viết từ zero.

  • Object code dissassembler (Dịch ngược asm):objdump
    Cho phép xem instruction code và mã dịch ngược về ASM của nó.

  • Profiler : gprof
    Là công cụ đánh giá performance của phần mềm trong Linux. Nó cho phép xác định hàm nào tốn nhiều thời gian xử lý nhất. Từ đó, ta có thể tập trung cải tiến để tăng hiệu năng.

4. Một chương trình Assembly đơn giản

Chương trình sau sẽ lấy tên của CPU và hiển thị ra dòng **The processor Vendor ID is XXXX **

  • Source code
#Author : Richard Blum
#cpuid.s Sample program to extract the processor Vendor ID
.section .data
output:
.ascii “The processor Vendor ID is ‘xxxxxxxxxxxx’\n”
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $output, %edi
movl %ebx, 28(%edi)
movl %edx, 32(%edi)
movl %ecx, 36(%edi)
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $42, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80

Lưu lại và đặt tên là cpuid.s:
– Biên dịch bằng Assembler:

$as -o cpuid.o cpuid.s

Output: cpuid.o

  • Link để tạo file chạy:
ld -o cpuid cpuid.o

Output: cpuid

  • Chạy
$ ./cpuid
The processor Vendor ID is 'GenuineIntel'
  • Debug
    Ta cần biên dịch với option khác thì mới debug được.
$as -o cpuid.o cpuid.s
ld -o cpuid cpuid.o

Ta sẽ đặt break point ở _start và xem thử vị trí dừng:

$ gdb ./cpuid
GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cpuid...done.
(gdb) b *_start
Breakpoint 1 at 0x8048074: file cpuid.s, line 10.
(gdb) r
Starting program: /home/osboxes/code/asm/cpuid

Breakpoint 1, _start () at cpuid.s:10
10 movl $0, %eax
(gdb) l
5 .section .text
6 .globl _start
7 _start:
8 # Using $0 for value 0
9 # Using %eax for register EAX
10 movl $0, %eax
11 cpuid
12 movl $output, %edi
13 movl %ebx, 28(%edi)
14 movl %edx, 32(%edi)
  • Dissassemler:
    Cho cpuid.o:
$ objdump -d cpuid.o

cpuid.o: file format elf32-i386

Disassembly of section .text:

00000000 :
0: b8 00 00 00 00 mov $0x0,%eax
5: 0f a2 cpuid
7: bf 00 00 00 00 mov $0x0,%edi
c: 89 5f 1c mov %ebx,0x1c(%edi)
f: 89 57 20 mov %edx,0x20(%edi)
12: 89 4f 24 mov %ecx,0x24(%edi)
15: b8 04 00 00 00 mov $0x4,%eax
1a: bb 01 00 00 00 mov $0x1,%ebx
1f: b9 00 00 00 00 mov $0x0,%ecx
24: ba 2a 00 00 00 mov $0x2a,%edx
29: cd 80 int $0x80
2b: b8 01 00 00 00 mov $0x1,%eax
30: bb 00 00 00 00 mov $0x0,%ebx
35: cd 80 int $0x80

Cho cpuid:

$ objdump -d cpuid

cpuid: file format elf32-i386

Disassembly of section .text:

08048074 :
8048074: b8 00 00 00 00 mov $0x0,%eax
8048079: 0f a2 cpuid
804807b: bf ab 90 04 08 mov $0x80490ab,%edi
8048080: 89 5f 1c mov %ebx,0x1c(%edi)
8048083: 89 57 20 mov %edx,0x20(%edi)
8048086: 89 4f 24 mov %ecx,0x24(%edi)
8048089: b8 04 00 00 00 mov $0x4,%eax
804808e: bb 01 00 00 00 mov $0x1,%ebx
8048093: b9 ab 90 04 08 mov $0x80490ab,%ecx
8048098: ba 2a 00 00 00 mov $0x2a,%edx
804809d: cd 80 int $0x80
804809f: b8 01 00 00 00 mov $0x1,%eax
80480a4: bb 00 00 00 00 mov $0x0,%ebx
80480a9: cd 80 int $0x80
Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s