Dịch sách Programming Ground Up Chương 3 Chương trình đầu tiên

Trong chương này bạn sẽ học cách viết và build chương trình hợp ngữ. Thêm vào đó, bạn sẽ học được cấu trúc của chương trình hợp ngữ, và một vài lệnh hợp ngữ. Trong suốt chương, bạn có thể tham khảo trong phụ lục B và F.

Các chương trình dưới đây có thể áp đảo bạn trong lần đầu tiên, vượt qua chúng bằng sử siêng năng, đọc code và các giải thích nhiều lần, và bạn sẽ có nền tảng để xây dựng chúng. Mỗi lần thất bại đều giúp bạn học điều gì đó.

Viết chương trình.

OK, chương trình đơn giản đầu tiên. Thực tế nó sẽ không làm gì cả!. Ngắn gọn, nhưng là cơ bản về hợp ngữ và lập trình linux. Nhập đoạn code vào editor của bạn và lưu với tên file exit.s. Đừng lo lắng nếu bạn không hiểu. Hãy chỉ gõ và chạy nó. Trong mục Outline of an Assembly Language Program chúng ta sẽ mô tả cách nó hoạt động

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#PURPOSE: Simple program that exits and returns a
# status code back to the Linux kernel
#
#INPUT: none
#
#OUTPUT: returns a status code. This can be viewed
# by typing
#
# echo $?
#
# after running the program
#
#VARIABLES:
# %eax holds the system call number
# %ebx holds the return status
#
.section .data
.section .text
.globl _start
_start:
movl $1, %eax # this is the linux kernel command
# number (system call) for exiting
# a program
movl $0, %ebx # this is the status number we will
# return to the operating system.
# Change this around and it will
# return different things to
# echo $?
int $0x80 # this wakes up the kernel to run
# the exit command

Cái bạn vừa gõ được gọi là source code. Source code là dạng đọc được của chương trình. Để biến đổi nó thành dạng máy tính có thể chạy được, chúng ta cần dịch assemble và link nó.

Bước đầu tiên là assemble. assembling là quá trình chuyển cái mà bạn gõ thành các chỉ thị lệnh cho máy. Bản thân máy tính chỉ đọc được tập hợp các con số, nhưng người lại thích chữ hơn. Hợp ngữ là human-readable hơn so với các chỉ thị cho máy tính hiểu. assembling chuyển các tập tin human-readable thành machine-readable. Để assembly gõ lệnh

1
as exit.s -o exit.o

as là lệnh cái mà chạy trình hợp dịch, exit.s là tập tin mã nguồn, -o exit.o nói với trình hợp dịch đặt output vào file exit.o, exit.o là object file. Một object file cũng là file ngôn ngữ máy, nhưng không hoàn toàn như thế. Hầu hết các chương trình lớn, bạn sẽ có vài file mã nguồn, bạn sẽ chuyển mỗi cái thành object file. Liker là chương trình chịu trách nhiệm đặt các object file cùng nhau và thêm thông tin vào nó, vì thể kernel biết cách tải và chạy chúng. Trong trường hợp của chúng ta chúng ta chỉ có một object file, vì thế linker chỉ thêm thông tin để cho phép nó chạy được. Để link file, nhập:

1
ld exit.o -o exit

ld là lệnh để chạy linker, exit.o là object file bạn muốn link, -o exit để chỉ ra file output là file exit. Nếu bất kỳ lệnh báo lỗi, bạn phải chạy lại tất cả các lệnh. Bạn chạy exit bằng cách gõ:

1
./exit

./ được sử dụng để nói với máy tính file thực thi ở thư mục hiện tại. Bạn sẽ được nhắc khi bạn gõ lệnh này, kết quả sẽ xuất hiện khi bạn gõ

1
echo $?

Kết quả là 0. Đây là exit status code, nó nói rằng mọi thứ đều đúng. Nếu mọi thứ đều ok nó sẽ trả về 0. UNIX program trả về số khác 0 nếu errors, warnings, hoặc statuses. Các lập trình viên định nghĩa các con số này. Bạn có thể xem nó khi gõ echo $?.

Outline of an Assembly Language Program

Hãy nhìn vào chương trình chúng ta đã nhập. Ở vị trí bắt đầu. Có nhiều dòng bắt đầu với ký tự hashs(#). Đây là chú thích (comment). Các chú thích không được dịch bởi trình hợp dịch. Chúng chỉ được sử dụng bởi lập trình viên để nói với những người khác, những người đọc code của họ trong tương lai. Hầu hết chương trình bạn viết sẽ được sửa bởi người khác. Hãy tập thói quen viết chú thích trong code của bạn. Luôn luôn bao gồm những thứ sau trong chú thích của bạn.

  • Mục đích của code.
  • Tổng quan về những thứ được xử lý.
  • Bất cứ thứ gì lạ trong chương trình bạn làm và tại sao lại vậy

Sau các dòng comment là dòng

.setion .data

Bất cứ thứ gì bắt đầu bằng dấu chấm không được dịch trực tiếp ra mã máy. Thay vào đó nó là chỉ thị cho trình biên dịch. Chúng được gọi là assembler directives hay pseudo-operations bởi vì chúng được handle bởi assembler. Lệnh .section chia chương trình ra thành các phần. Lệnh trên để bắt đầu section data. Chương trình của chúng ta không sử dụng gì khác, vì thế chúng ta không cần phần này. Hầu hết các chương trình bạn viết trong tương lai sẽ có data.

Ngay sau đó là

.section .text

cái mà bắt đầu text section. Text section của chương trình là nơi các lệnh chương trình cư trú.

.globl _start
Chỉ thị này giúp trình hợp dịch biết _start là quan trọng để nhớ. _start là một symbol, nghĩa là

_start
Định nghĩa giá trị _start. Một label là một symbol theo sau bởi một dấu colon. Labels định nghĩa các giá trị của symbol’s value. Khi trình hợp dịch, hợp dịch chương trình, nó phải gán mỗi dữ liệu và chỉ thị một địa chỉ. Labels nói cho trình hợp dịch tạo các giá trị

movl $1, %eax

Khi chương trình chạy, chỉ thị này chuyển số 1 vào thanh ghi %eax. Trong hợp ngữ, nhiều chỉ thị có các toán hạng. movl có 2 toán tử - the source và the destination. Trong trường hợp này, the source là số 1, và the destination là thanh ghi %eax. Các toán tử có thể là số, địa chỉ hoặc thanh ghi. Các chỉ thị khác nhau cho phép các toán tử khác nhau. Xem phụ lục B để xem thêm về các chỉ thị nhận các kiểu toán tử nào.

Hầu hết các chỉ thị có 2 toán hạng, đầu tiên là nguồn (source), thứ 2 là đích (destination). Trong hầu hết trường hợp source không bị sửa đổi.

Trong x86 processor, có vài thanh ghi đa mục đích (tất cả đều có thể sử dụng movl):

  • %eax, %ebx, %ecx, %edx, %edi, %esi

Thêm vào đó có vài thanh ghi với mục đích đặc biệt:

  • %ebp, %esp, %eip, %eflags

Chúng ta sẽ thảo luận sau. Một vài thanh ghi như %eip và %eflags có thể được truy cập bởi các chỉ thị đặc biệt. Các cái khác có thể truy cập sử dụng cùng chỉ thị các thanh ghi đa mục tiêu.

Vì thếm lệnh movl di chuyển số 1 vào thanh ghi %eax. Ký hiệu $ ở trước để nói rằng chúng ta muốn sử dụng immediate mode addressing (refer back to Section Data Accessing Methods trong chương 2). Nếu không có dấu $ nó sẽ là sử dụng direct addressing mode, tải số ở địa chỉ 1. Chúng ta muốn sử dụng số 1 để load vào nên chúng ta phải sử dụng immediate mode.

Lý do để chúng ta di chuyển số 1 vào thanh ghi %eax là bởi vì chúng ta chuẩn bị gọi Linux Kernel. Số 1 là số của exit system call. Chúng ta sẽ thảo luận về system calls sâu hơn sắp tới, nhưng cơ bản chúng ta sẽ yêu cầu hệ điều hành giúp. Các chương trình bình thường không làm tất cả. Nhiều hoạt động như gọi chương trình khác, làm việc với tập tin hay thoát phải được handled bởi hệ điều hành thông qua system calls. Khi bạn muốn tạo system calls, đơn giản chúng ta sẽ load một số vào thanh ghi %eax (Một danh sách tất cả system calls và số tương ứng có trong phụ lục C). Phụ thuộc vào system call, các thanh ghi khác có thể có những giá trị khác. System calls không phải mục đích chính của các thanh ghi. Các chương trình sau sẽ sử dụng các thanh ghi cho các tác vụ tính toán.

Hệ điều hành, cần các thông tin để thực hiện system calls. Ví dụ, để dealing with files, hệ điều hành cần biết được tập tin nào cần mở, dữ liệu nào cần được ghi và các thứ khác. Các thông tin chi tiết thêm vào được gọi là các tham số được lưu trữ ở các thanh ghi. Trong trường hợp exit system calls, hệ điều hành yêu cầu status code được load vào thanh ghi %ebx. Giá trị này sẽ được trả về cho hệ thống. Giá trị này bạn có có được khi bạn gõ echo $?. Vì thế, chúng ta tải %ebx với 0 bằng cách gõ: movl $0, %ebx

1
2
3
Phần chân trang 26:

Tiền tố e để chỉ định phiên bản mở rộng của thanh ghi. Thương xuyên bạn sẽ sử dụng các phiên bản mở rộng. Model mới hơn là phiên bản 64 bit, các tahanh ghi với tiền tố r nói đến các thanh ghi lớn hơn (ví dụ rax là 64 bit version của %eax). Tuy nhiên các vi xử lý không được sử dụng rộng rãi không được bao gồm trong cuốn sách này.

Bây giờ, Việc tải các số vào thanh ghi không có tác dụng gì. Các thanh ghi được sử dụng cho nhiều mục đích không chỉ là system calls. Chúng là nơi các program logic như cộng, trừ, so sánh sử dụng. Linux yêu cầu các thanh ghi nhất định được tải với các tham số nhất định trước khi tạo một system call. %eax luôn yêu cầu được sử dụng với một con số. Tuy nhiêu mỗi system call lại yêu cầu khác nhau. Như trong exit system call, %eba được yêu cầu được nạp giá trị exit stattus. Chúng ta sẽ thảo luận về các system calls khác khi cần. Chỉ thị tiếp theo:

int $0x80

int viết tắt của interupt. 0x80 là interupt number được sử dụng. Một interunp sẽ ngắt luồng chương trình, và chuyển luồng điểu khiển chương trình của chúng ta cho Linux, nó sẽ gọi lệnh system call.

Bạn có thể không hiểu tại sao dùng 0x80 thay vì 80. Lý do là số chúng ta viết ở dạng cơ số 16.

Lên kế hoạch cho chương trình

Trong chương trình tiếp theo, chúng ta sẽ tìm số lớn nhất trong danh sách các số. Chúng ta cần lập kế hoạch một cách chi tiết:

  • Nơi lưu danh sách các số?
  • Thủ tục cần để tìm số lớn nhất?
  • Chúng ta cần bao nhiêu bộ nhớ?
  • Tất cả có thể lưu vừa trong các thanh ghi, hay chúng ta cần thêm bộ nhớ.

Bạn có thể nghĩ rằng thật đơn giản để tìm số lớn nhất trong danh sách. Bạn có thể hỏi vài người cách tìm số lớn nhất, họ có thể gặp vài rắc rối. Tuy nhiên, trong đầu chúng ta có thể làm các tác vụ phức tạp một cách tự động. Máy tính cần được chỉ thị.

Chúng ta cần các thanh ghi:

  • %edi sẽ giữ vị trí hiện tại trong danh sách
  • %ebx sẽ giữ giá trị lớn nhất trong danh sách
  • %eax sẽ giữ phần tử hiện tại

Khi chúng ta bắt đầu chương trình hãy xem giá trị đầu tiên trong danh sách.

Finding a Maximum Value