极客算法

汇编语言01 - 介绍

2024-05-11
OS

进制

Decimal                              Binary

25 ÷ 2= 12 r 1                       11001 ÷ 10= 1100 r 1
12 ÷ 2 = 6 r 0                       1100 ÷ 10 = 110 r 0
6 ÷ 2 = 3 r 0                        110 ÷ 10 = 11 r 0
3 ÷ 2 = 1 r 1                        11 ÷ 10 = 1 r 1
1 ÷ 2 = 0 r 1                        1 ÷ 10 = 0 r 1

2510(十进制) = 11001(二进制)

This method finds the rightmost digit first, this digit is called the least significant bit (lsb).

The leftmost digit is called the most significant bit (msb).

CPU寄存器

8086 16-bit CPU

16个16-bit通用寄存器

  • AX, BX, CX, DX 能被分成两个8bit部分使用, 例如AX=AH + AL, 用来数据移动和计算指令
  • SI, DI 作为指针(Pointers), 无法分割
  • BP, SP 栈指针
  • CS, DS, SS, ES 分段(segment)寄存器, 分别是:Code Segment, Data Segment, Stack Segment 和 Extra Segment. 其中ES作为temporary segment register
  • IP 指令寄存器, CS是指向代码。当指令执行,IP指向内存中下一个要执行的指令
  • FLAGS 按照bit位存储的,一些特殊的开关

80386 32-bit CPU

  • 新增了32-bit版本EAX,为了保持兼容AX仍然是16-bit版本,AX指向EAX的低16位
  • segemnt寄存器仍然16-bit, 新增FS和GS, 和ES一样,是额外的两个temporary segment register

内存模式

Real Mode

真实模式访问限制1MB, 地址范围=[0x00000, 0xFFFFF], 若要表示这些地址需要20-bit寄存器。为了解决这个问题,intel使用2个16-bit值表示内存地址,selector + offset, 公式如下:

16 ∗ selector + offset

其中selector必须在segment寄存器中。 计算的时候,selector左移一位 + offset就可以了

问题1:1个selector只能限制64K大小内存, 当程序大于64K时,需要切分成小于64K的segments,当执行一个segment结尾时,CS需要改变。其他的segment寄存器,也会遇到类似的问题

问题2: 同一个物理地址04808可以写成047C:0048, 047D:0038, 047E:0028, 047B:0058

即使这样,由于BIOS启动时没有操作系统,所以只能只用这种模式

16-bit Protected Mode

在80286的保护模式中,使用virtual memeory技术, 该技术只保存使用中的数据和代码在内存中,其他未使用的放在磁盘中。操作系统作为搬运工

selector在概念上指向了虚拟内存的描述表(descriptor table),不再是真实的内存段落(paragraph)

此时,每个段寄存器指向描述表的index,操作系统知道该index的所有信息,包括是否当前在内存中,如果在物理地址在哪里,访问权限(只读等)

此时,问题1仍然存在,依然是64K大小限制

32-bit Protected Mode

在80386中,offset使用的是32位,这样segment就可以访问4G的大小。同时,segment能够被切分成更小的pages,每一个page=4K大小,

在80286中,整个segment要么在内存中,要么完全不在内存中。由于segment在32位中太大。virtual memeory不再使用segment转而使用Page

汇编

每种CPU都有自己的机器语言,指令是内存中按照bytes储存的数,指令对应的数叫operation code简称opcode

03 C3

这个指令,将EAX和EBX加起来,并且将结果存储在EAX中。

汇编语言就是相对机器语言的高级语言,汇编使用的是text, 每一个汇编指令对应一条机器指令。

add eax, ebx

assembler就是将汇编语言翻译成机器码的解析程序

相比高级语言,汇编语言的可移植性很差,因为每个CPU对应的opcode不同,也就有不同的汇编语言

语法

mnemonic operand(s)

其中operands一般1~3个主要以下4种:

  • register: 寄存器
  • memory: 常量或变量,内存终是一个segment的偏移量(offset)
  • immediate: 指令中的常量
  • implied: 隐含变量,比如对register或memory做increment操作

基本指令

MOV

mov dest src: 将src中的值拷贝到dest中, 储存src, dest的容器必须同样大小

mov eax, 3  ; eax = 3
mov bx, ax  ; bx = ax

ADD

add eax, 4 ; eax = eax + 4
add al, ah ; al = al + ah

SUB

sub bx, 10   ; bx = bx - 10
subb ebx, edi; ebx = ebx - edi

INC

inc ecx ; ecx++

DEC

dec dl ; dl--

Directive

directive是编译器的发明,并不直接翻译到机器码中

  • 定义常量
  • 定义内存存储数据
  • 将内存组成segments
  • 条件包含代码
  • 引用其他文件

equ

symbol equ value

symbol不能再次定义

define

%define SIZE 100
move eax, SIZE

data

Unit Letter
byte B
word W
double word D
quad word Q
ten bytes T
L1 db 0                         ; byte L1 = 0
L2 dw 1000                      ; Word L2 = 1000
L3 db 110101b                   ; byte L3 = 0b110101,十进制=53
L4 db 12h                       ; byte L4 = 0x12, 十进制=18
L5 db 17o                       ; byte L5 = 0o17, 十进制=15
L6 dd 1A92h                     ; double word L6 = hex(1A92)
L7 resb 1                       ; byte L7
L8 db "A"                       ; byte L8 = ASCII(A)
L9 db 0,1,2,3                   ; byte L9 = [0, 1, 2, 3]

L10 db "w", "o", "r", "d", 0    ; string L10 = "word"
L11 db 'word', 0                : string L11 = "word"

L12 times 100 db 0              ; byte L12[100] = {0}
L13 resw 100                    ; word L13[100]

Label和Pointer类似,直接使用就是地址,[Label]就是地址里面的值

mov    al, [L1]     ; copy byte at L1 into AL
mov    eax, L1      ; EAX = address of byte at L1
mov    [L1], ah     ; copy AH into byte at L1
mov    eax, [L6]    ; copy double word at L6 into EAX 
add    eax, [L6]    ; EAX = EAX + doubleword at L6
add    [L6], eax    ; double word at L6 += EAX
mov    al, [L6]     ; copy first byte of double word at L6 into AL

类型

Unit Letter
byte BYTE
word WORD
double word DWORD
quad word QWORD
ten bytes TWORD
mov [L6], 1         ; store a 1 at L6, ❌operation size not specified error
mov dword [L6], 1

打印与调试

%include "asm_io.asm"
Function Register
print_int EAX
print_char AL
print_string EAX
print_nl -
read_int EAX
reac_char EAX
%include "asm_io.inc"
Macro params
dump_regs label(integer)
dump_mem label(interger), address, number(16-byte paragraphs after address)
dump_stack label(interger), number(double words below EBP), number(double words above EBP)
dump_math label(interger)

第一个程序

程序调用

使用C有几个好处:

  1. C语言会在保护模式中,所有segment寄存器也会被设置好.
  2. C语言中的库函数,也能在汇编语言中使用
#if defined(__GNUC__)
#  define PRE_CDECL
#  define POST_CDECL __attribute__((cdecl))
#else
#  define PRE_CDECL __cdecl
#  define POST_CDECL
#endif

int PRE_CDECL asm_main( void ) POST_CDECL;

int main()
{
  int ret_status;
  ret_status = asm_main();
  return ret_status;
}

汇编函数

功能: 提示输入两个数,然后输出量数之和并打印出来

所有初始化的变量都在.data segment中, 由于使用printf, 字符串结尾要加个0 为初始化的变量在.bss segment中,是一个stack segment

segment .data
;
; These labels refer to strings used for output
;
prompt1 db    "Enter a number: ", 0       ; don't forget nul terminator
prompt2 db    "Enter another number: ", 0
outmsg1 db    "You entered ", 0
outmsg2 db    " and ", 0
outmsg3 db    ", the sum of these is ", 0

segment .bss
;
; These labels refer to double words used to store the inputs
;
input1  resd 1
input2  resd 1

代码放在.text segment中,由于C调用标准差异,macOS Windows asm_main 前有下划线. Linux则没有 默认标识符在module中,使用global可以被其他module调用

;
; code is put in the .text segment
;
segment .text
        global  asm_main
asm_main:
        enter   0,0               ; setup routine
        pusha

        mov     eax, prompt1      ; print out prompt
        call    print_string

        call    read_int          ; read integer
        mov     [input1], eax     ; store into input1

        mov     eax, prompt2      ; print out prompt
        call    print_string

        call    read_int          ; read integer
        mov     [input2], eax     ; store into input2

        mov     eax, [input1]     ; eax = dword at input1
        add     eax, [input2]     ; eax += dword at input2
        mov     ebx, eax          ; ebx = eax
        dump_regs 1               ; dump out register values
        dump_mem 2, outmsg1, 1    ; dump out memory
;
; next print out result message as series of steps
;
        mov     eax, outmsg1
        call    print_string      ; print out first message
        mov     eax, [input1]     
        call    print_int         ; print out input1
        mov     eax, outmsg2
        call    print_string      ; print out second message
        mov     eax, [input2]
        call    print_int         ; print out input2
        mov     eax, outmsg3
        call    print_string      ; print out third message
        mov     eax, ebx
        call    print_int         ; print out sum (ebx)
        call    print_nl          ; print new-line

        popa
        mov     eax, 0            ; return back to C
        leave                     
        ret

执行结果

root@vultr:~/pcasm/examples/linux# ./first
Enter a number: 1
Enter another number: 23
Register Dump # 1
EAX = 00000018 EBX = 00000018 ECX = 2CDAC02A EDX = FF9F77F0
ESI = FF9F7884 EDI = F7FF7B80 EBP = FF9F77A8 ESP = FF9F7788
EIP = 5658D237 FLAGS = 0206                PF
Memory Dump # 2 Address = 56590030
56590030 59 6F 75 20 65 6E 74 65 72 65 64 20 00 20 61 6E "You entered ? an"
56590040 64 20 00 2C 20 74 68 65 20 73 75 6D 20 6F 66 20 "d ?, the sum of "
You entered 1 and 23, the sum of these is 24

汇编模版

;
; file: skel.asm
; This file is a skeleton that can be used to start assembly programs.

%include "asm_io.inc"
segment .data
;
; initialized data is put in the data segment here
;


segment .bss
;
; uninitialized data is put in the bss segment
;

segment .text
        global  asm_main
asm_main:
        enter   0,0               ; setup routine
        pusha

;
; code is put in the text segment. Do not modify the code before
; or after this comment.
;

        popa
        mov     eax, 0            ; return back to C
        leave                     
        ret

–END–


评论

内容:
其他: