Cùng viết Assembly Language giản lược bằng asmrb gem
I. Giới thiệu chung về assembly Assembly Language là ngôn ngữ "cổ" nhất trong lịch sự, và ngày nay nó gần như trở nên "vô hình" đối với lập trình viên những thế hệ sau này, nhờ sự ra đời của vô số ngôn ngữ hiện đại dựa trên các máy ảo [VM - virtual machine] và trình biên dịch/thông dịch ...
I. Giới thiệu chung về assembly
Assembly Language
là ngôn ngữ "cổ" nhất trong lịch sự, và ngày nay nó gần như trở nên "vô hình" đối với lập trình viên những thế hệ sau này, nhờ sự ra đời của vô số ngôn ngữ hiện đại dựa trên các máy ảo [VM - virtual machine] và trình biên dịch/thông dịch [compiler/interpreter] thông minh.
Virtual Machine
Sự trừu tượng hoá các thành phần bên dưới [low-level abstraction] khiến chúng ta không còn phải giao tiếp trực tiếp với assembly. Thay vào đó, các máy ảo/trình biên dịch sẽ đảm nhận nhiệm vụ biên dịch các ngôn ngữ bậc cao [high-level language] xuống assembly phù hợp với các kiến trúc CPU mà ta lựa chọn như x86, 64, arm ..
Low-Level
Assembly rất gần với phần cứng bên dưới, nó thực hiện phần lớn các tác vụ thông qua các lệnh [instruction], thanh ghi CPU [Register: EAX, EBX...] và bộ nhớ động RAM [Random Access Memory]. Do đặc thù như vậy, nên nó thường được dùng để viết những chức năng cấp dưới, và thiên về phần cứng nhiều hơn là ứng dụng thực sự bên trên.
Stack-based Evaluation
Một cách đơn giản thì assembly có cách thực thi chương trình dựa trên bộ nhớ kiểu ngăn xếp [Stack] giống với các ngôn ngữ hậu thứ tự [PostFix language]:
Thay vì viết: 1 + 2
Ta viết : 1 2 +
Theo kiểu viết của assembly, nó sẽ thực hiện theo trình tự:
push value 1 on stack push value 2 on stack call function :add => result is 3
Đó là cách ngôn ngữ này thực hiện phần lớn các tác vụ.
Ngoài ra, để rẽ nhánh [branching] và tạo vòng lặp [routine], asmrb sẽ nhảy [jump] đến các nhãn [label] được đánh dấu trong thân chương trình. Tính năng này tương tự với goto ở Basic hay C#.
II. Asmrb - code assembly giản lược trên Ruby
Mình xin giới thiệu gem asmrb của Ruby, một tiện ích giúp bạn làm quen với assembly.
** Cài Đặt **
> gem install asmrb
** Source code **
https://github.com/thetrung/asmrb
** Bắt Đầu **
khai báo sử dụng gem asmrb:
require "asmrb"
Tạo một block khi khởi tạo object của Asmrb:
f = Asmrb.new do .... end
Ok, giờ là code của asmrb. Hãy khai báo 1 hàm mới với fun và tên hàm ở dạng symbol:
f = Asmrb.new do fun :factorial ret end
một hàm trong asmrb hay assembly, thường kết thúc bằng ret. Ngoài việc kết thúc chương trình, nó còn trả về giá trị trên cùng [còn gọi là top-most] của stack.
một hàm thường có các tham số [parameter], ta khai báo bằng lệnh arg :acc, :n.
f = Asmrb.new do fun :factorial arg :acc, :n ret end
Để sử dụng tham số hay giá trị, chúng cần được đẩy lên stack bằng psh:
f = Asmrb.new do fun :demo psh 1 psh 2 end f.invoke => [1, 2] > demo
Và đẩy đối tượng top-most ra khỏi stack bằng pop:
pop dbg end f.invoke => [1, 2] > demo => [1] > demo
Quay trở lại hàm :factorial, ta hãy thử xem hàm này trên Ruby:
def factorial acc, n if n < 1 acc else factorial acc * n, n - 1 end end
Như vậy, cần kiểm tra điều kiện n < 1:
true: trả về acc false: gọi factorial với 2 biểu thức acc * n và n - 1
Biểu thức if n < 1
Theo hậu thứ tự: n 1 < dùng psh ta có thể viết:
psh 1 psh :n
Thêm jlt để so sánh n < 1 và nhảy đến block :result nếu thoả mãn điều kiện:
psh 1 psh :n jlt :result
Code của hàm factorial hiện tại sẽ trở thành:
f = Asmrb.new do fun :factorial arg :acc, :n psh 1 psh :n jlt :result ret end
* Khi điều kiện chưa thoả mãn, asmrb sẽ tiếp tục thực thi các lệnh kế tiếp như vế else của nó vậy.
Để tạo 1 block code, hay thực ra là tạo 1 label để asmrb nhảy đến, ta dùng lệnh blo:
blo :else
ở đây, ta khởi tạo block :else để tiếp tục phần đệ quy factorial, với 2 biểu thức đã nói: acc * n và n - 1.
Trước hết ta sẽ viết 2 biểu thức này với phép nhân là mul và phép trừ dùng sub:
psh :n psh :acc mul psh 1 psh n sub
Và giờ gọi lại hàm factorial bằng lệnh rec, nói cách khác, lệnh này luôn tự nhảy về label :factorial được tạo nhờ fun:
rec
Lúc này, asmrb sẽ thực thi như sau:
- Giá trị của 2 biểu thức acc * n và n - 1 kia sẽ được lần lượt đẩy lên stack
- rec sẽ nhảy về block :factorial, và gán 2 giá trị trên cùng của stack cho :acc và :n
Nhìn lại factorial:
f = Asmrb.new do fun :factorial arg :acc, :n psh 1 psh :n jlt :result blo :else psh :n psh :acc mul psh 1 psh n sub rec ret end
Có vẻ khá dài, chúng ta có thể rút ngắn lại bằng cách viết gộp các lệnh psh bằng los:
psh 1 psh 2 psh 3 # became: los 1, 2, 3 # evaluation: => [1,2,3] > function
factorial sẽ trở thành:
f = Asmrb.new do fun :factorial arg :acc, :n los :n, 1 jlt :result blo :else los :acc, :n mul los :n, 1 sub rec ret end
* los sẽ tự đảo ngược thứ tự phần tử được đẩy lên stack nên ta không cần viết ngược thứ tự như psh.
Cuối cùng, ta viết block :result để trả về giá trị của :acckhi n < 1 thoả mãn:
blo :result psh :acc ret end
Hàm factorial đã hoàn thiện:
f = Asmrb.new do fun :factorial arg :acc, :n los :n, 1 jlt :result blo :else los :acc, :n mul los :n, 1 sub rec blo :result psh :acc ret end
* Khi muốn xem giá trị hiện tại của stack, ta có thể chèn thêm dbg vào:
f = Asmrb.new do fun :demo psh 1 dbg end f.invoke => [1] > demo
Để chạy hàm này, ở chế độ thông dịch qua asmrb cùng debug cách bước chạy, thêm phần:
f.is_debug = true result = f.invoke 1, 3 puts result
kết quả ở console:
---------------------- [1, 3] > factorial 0: arg acc n 1: los n 1 2: jlt result 3: los acc n 4: mul 5: los n 1 6: sub 7: rec ::[factorial] 0: arg acc n 1: los n 1 2: jlt result 3: los acc n 4: mul 5: los n 1 6: sub 7: rec ::[factorial] 0: arg acc n 1: los n 1 2: jlt result 3: los acc n 4: mul 5: los n 1 6: sub 7: rec ::[factorial] 0: arg acc n 1: los n 1 2: jlt result ::[result] 8: psh acc 9: ret ---------------------- 6
Ở console-log trên, ta có thể thấy:
- chương trình nhận tham số [1, 3] > factorial,
- Sau đó chạy đến khi gặp lệnh rec, và nhảy về đầu chương trình ::[factorial] ,
- Cứ thế lặp đến khi nhảy đến block ::[result]
- Nó trả lại kết quả về stack và thoát chương trình.
6
Ta nhận đc kết quả phép tính sau khi invoke là 6.
III. Tổng quan
Trên đây chỉ là một ví dụ về cách viết một chương trình assembly đã được "giản lược" đi rất nhiều thành phần [ như Register, Memory Pointer, Memory Address ...] để nó trở nên đơn giản và gần gũi hơn với ngôn ngữ bậc cao.
Hy vọng các bạn sẽ tìm thấy nhiều điều thú vị với gem này. Do đang trong giai đoạn demo bản mẫu nên asmrb chưa thật sự hoàn thiện. Mọi ý kiến / đóng góp xin đừng ngần ngại gửi về github của trên, mình xin cảm ơn (bow)