这个程序出自《Assembly Language step by step programming with linux》第11章,这是一个相对复杂一点的程序,将会用到我们前面几节学习的全部指令。
SECTION .data ; Section containing initialised data
EOL equ 10 ; Linux end-of-line character
FILLCHR equ 32 ; ASCII space character
HBARCHR equ 95 ; Use dash char if this won't display
STRTROW equ 2 ; Row where the graph begins
; The dataset is just a table of byte-length numbers:
Dataset db 9,17,71,52,55,18,29,36,18,68,77,63,58,44,0
Message db "Data current as of 1/9/2015"
MSGLEN equ $-Message
; This escape sequence will clear the console terminal and place the
; text cursor to the origin (1,1) on virtually all Linux consoles:
ClrHome db 27,"[2J",27,"[01;01H"
CLRLEN equ $-ClrHome ; Length of term clear string
SECTION .bss ; Section containing uninitialized data
COLS equ 81 ; Line length + 1 char for EOL
ROWS equ 25 ; Number of lines in display
VidBuff resb COLS*ROWS ; Buffer size adapts to ROWS & COLS
SECTION .text ; Section containing code
global _start ; Linker needs this to find the entry point!
; This macro clears the Linux console terminal and sets the cursor position
; to 1,1, using a single predefined escape sequence.
%macro ClearTerminal 0
pushad ; Save all registers
mov eax,4 ; Specify sys_write call
mov ebx,1 ; Specify File Descriptor 1: Standard Output
mov ecx,ClrHome ; Pass offset of the error message
mov edx,CLRLEN ; Pass the length of the message
int 80H ; Make kernel call
popad ; Restore all registers
%endmacro
Show: pushad ; Save all registers
mov eax,4 ; Specify sys_write call
mov ebx,1 ; Specify File Descriptor 1: Standard Output
mov ecx,VidBuff ; Pass offset of the buffer
mov edx,COLS*ROWS ; Pass the length of the buffer
int 80H ; Make kernel call
popad ; Restore all registers
ret ; And go home!
ClrVid: push eax ; Save caller's registers
push ecx
push edi
cld ; Clear DF; we're counting up-memory
mov al,FILLCHR ; Put the buffer filler char in AL
mov edi,VidBuff ; Point destination index at buffer
mov ecx,COLS*ROWS ; Put count of chars stored into ECX
rep stosb ; Blast chars at the buffer
; Buffer is cleared; now we need to re-insert the EOL char after each line:
mov edi,VidBuff ; Point destination at buffer again
dec edi ; Start EOL position count at VidBuff char 0
mov ecx,ROWS ; Put number of rows in count register
PtEOL: add edi,COLS ; Add column count to EDU
mov byte [edi],EOL ; Store EOL char at end of row
loop PtEOL ; Loop back if still more lines
pop edi ; Restore caller's registers
pop ecx
pop eax
ret ; and go home!
WrtLn: push eax ; Save registers we change
push ebx
push ecx
push edi
cld ; Clear DF for up-memory write
mov edi,VidBuff ; Load destination index with buffer address
dec eax ; Adjust Y value down by 1 for address calculation
dec ebx ; Adjust X value down by 1 for address calculation
mov ah,COLS ; Move screen width to AH
mul ah ; Do 8-bit multiply AL*AH to AX
add edi,eax ; Add Y offset into vidbuff to EDI
add edi,ebx ; Add X offset into vidbuf to EDI
rep movsb ; Blast the string into the buffer
pop edi ; Restore registers we changed
pop ecx
pop ebx
pop eax
ret ; and go home!
WrtHB: push eax ; Save registers we change
push ebx
push ecx
push edi
cld ; Clear DF for up-memory write
mov edi,VidBuff ; Put buffer address in destination register
dec eax ; Adjust Y value down by 1 for address calculation
dec ebx ; Adjust X value down by 1 for address calculation
mov ah,COLS ; Move screen width to AH
mul ah ; Do 8-bit multiply AL*AH to AX
add edi,eax ; Add Y offset into vidbuff to EDI
add edi,ebx ; Add X offset into vidbuf to EDI
mov al,HBARCHR ; Put the char to use for the bar in AL
rep stosb ; Blast the bar char into the buffer
pop edi ; Restore registers we changed
pop ecx
pop ebx
pop eax
ret ; And go home!
Ruler: push eax ; Save the registers we change
push ebx
push ecx
push edi
mov edi,VidBuff ; Load video address to EDI
dec eax ; Adjust Y value down by 1 for address calculation
dec ebx ; Adjust X value down by 1 for address calculation
mov ah,COLS ; Move screen width to AH
mul ah ; Do 8-bit multiply AL*AH to AX
add edi,eax ; Add Y offset into vidbuff to EDI
add edi,ebx ; Add X offset into vidbuf to EDI
; EDI now contains the memory address in the buffer where the ruler
; is to begin. Now we display the ruler, starting at that position:
mov al,'1' ; Start ruler with digit '1'
DoChar: stosb ; Note that there's no REP prefix!
add al,'1' ; Bump the character value in AL up by 1
aaa ; Adjust AX to make this a BCD addition
add al,'0' ; Make sure we have binary 3 in AL's high nybble
loop DoChar ; Go back & do another char until ECX goes to 0
pop edi ; Restore the registers we changed
pop ecx
pop ebx
pop eax
ret ; And go home!
;-------------------------------------------------------------------------
; MAIN PROGRAM:
_start:
nop ; This no-op keeps gdb happy...
; Get the console and text display text buffer ready to go:
ClearTerminal ; Send terminal clear string to console
call ClrVid ; Init/clear the video buffer
; Next we display the top ruler:
mov eax,1 ; Load Y position to AL
mov ebx,1 ; Load X position to BL
mov ecx,COLS-1 ; Load ruler length to ECX
call Ruler ; Write the ruler to the buffer
; Here we loop through the dataset and graph the data:
mov esi,Dataset ; Put the address of the dataset in ESI
mov ebx,1 ; Start all bars at left margin (X=1)
mov ebp,0 ; Dataset element index starts at 0
.blast: mov eax,ebp ; Add dataset number to element index
add eax,STRTROW ; Bias row value by row # of first bar
mov cl,byte [esi+ebp] ; Put dataset value in low byte of ECX
cmp ecx,0 ; See if we pulled a 0 from the dataset
je .rule2 ; If we pulled a 0 from the dataset, we're done
call WrtHB ; Graph the data as a horizontal bar
inc ebp ; Increment the dataset element index
jmp .blast ; Go back and do another bar
; Display the bottom ruler:
.rule2: mov eax,ebp ; Use the dataset counter to set the ruler row
add eax,STRTROW ; Bias down by the row # of the first bar
mov ebx,1 ; Load X position to BL
mov ecx,COLS-1 ; Load ruler length to ECX
call Ruler ; Write the ruler to the buffer
; Thow up an informative message centered on the last line
mov esi,Message ; Load the address of the message to ESI
mov ecx,MSGLEN ; and its length to ECX
mov ebx,COLS ; and the screen width to EBX
sub ebx,ecx ; Calc diff of message length and screen width
shr ebx,1 ; Divide difference by 2 for X value
mov eax,ROWS-1 ; Set message row to Line 24
call WrtLn ; Display the centered message
; Having written all that to the buffer, send the buffer to the console:
call Show ; Refresh the buffer to the console
Exit: mov eax,1 ; Code for Exit Syscall
mov ebx,0 ; Return a code of zero
int 80H ; Make kernel call
程序分析:
ClearTerminal宏,用于清屏,并且把光标移动到第一行第一列的位置。
Show函数,用于把VidBuff的内容显示到标准输出。
ClrVid函数,用于清理VidBuff缓存。
ClrVid: push eax
push ecx
push edi
cld //清除DF标志位,控制内存地址变化方向从低到高。
mov al,FILLCHR //设置al为空格
mov edi,VidBuff //edi= VidBuff,目的缓存是VidBuff
mov ecx,COLS*ROWS //填充字符数等于缓存的字节数。
rep stosb //把al的值循环填充到VidBuff中,循环次数是缓存的字节数。
mov edi,VidBuff //edi= VidBuff,目的缓存是VidBuff
dec edi //edi = VidBuff -1
mov ecx,ROWS //ecx=ROWS,循环次数是行数。
PtEOL: add edi,COLS edi = edi + COLS,这样edi是每行的最后一列
mov byte [edi],EOL //把最后一列设置为换行符
loop PtEOL //循环次数是ROWS。
pop edi
pop ecx
pop eax
ret
WrtLn函数的输入参数包括eax,ebx,ecx和esi。其中eax和ebx,分别表示Y和X坐标,ecx表示源字符串长度,esi是源字符串地址。此函数会把源字符串拷贝到以此坐标为起点的VidBuff中。
WrtLn: push eax
push ebx
push ecx
push edi
cld //清除DF标志位,控制内存地址变化方向从低到高。
mov edi,VidBuff //edi= VidBuff
dec eax //eax=eax-1,y坐标是从1开始的,因此计算地址时要减1。
dec ebx //ebx=ebx-1,x坐标是从1开始的,因此计算地址时要减1。
mov ah,COLS //ah = COLS
mul ah //ax=ah*al
add edi,eax //edi = VidBuff+ COLS*(y-1)
add edi,ebx //edi= VidBuff+ COLS*(y-1)+(x-1)
rep movsb //内存拷贝,拷贝ecx个字节
pop edi
pop ecx
pop ebx
pop eax
ret
WrtHB函数,用于画圆柱,因为屏幕显示的问题,实际画的是下划线。入参与WrtLn函数差不多,只是没有esi。
WrtHB: push eax
push ebx
push ecx
push edi
cld //清除DF标志位,控制内存地址变化方向从低到高。
mov edi,VidBuff //edi= VidBuff
dec eax
dec ebx
mov ah,COLS
mul ah //ax=ah*al
add edi,eax //edi = VidBuff+ COLS*(y-1)
add edi,ebx //edi= VidBuff+ COLS*(y-1)+(x-1)
mov al,HBARCHR //al= HBARCHR
rep stosb //用al循环填充edi指向的缓存,循环次数是ecx
pop edi
pop ecx
pop ebx
pop eax
ret
Ruler函数,用于画标尺,入参与WrtHB函数一样。
Ruler: push eax
push ebx
push ecx
push edi
mov edi,VidBuff //edi= VidBuff
dec eax
dec ebx
mov ah,COLS
mul ah //ax=ah*al
add edi,eax //edi = VidBuff+ COLS*(y-1)
add edi,ebx //edi= VidBuff+ COLS*(y-1)+(x-1)
mov al,'1' //标尺起始数字是’1’
DoChar: stosb
add al,'1' //两个数字的ASCII码相加。
aaa //跳转ax寄存器,使得al是一个合法的BCD值。
add al,'0' //加上’0’,使得al是’0’到’9’之间的ASCII码。
loop DoChar //循环ecx次
pop edi
pop ecx
pop ebx
pop eax
ret
主程序
ClearTerminal //清屏
call ClrVid //初始化缓存
mov eax,1
mov ebx,1
mov ecx,COLS-1
call Ruler //第一个标尺输入缓存
mov esi,Dataset //esi= Dataset
mov ebx,1 //x=1
mov ebp,0 //ebp=0
.blast: mov eax,ebp //eax=ebp
add eax,STRTROW //eax=eax+ STRTROW,y值
mov cl,byte [esi+ebp] //cl=esi[ebp],传入长度
cmp ecx,0 //比较ecx和0
je .rule2 //如果ecx等于0,跳转到.rule2,结束循环
call WrtHB //画圆柱
inc ebp //ebp=ebp+1
jmp .blast //跳转到.blast,继续循环
.rule2: mov eax,ebp //eax=ebp
add eax,STRTROW // eax=eax+ STRTROW,y值
mov ebx,1 //x=1
mov ecx,COLS-1 //ecx= COLS-1
call Ruler //画第二个标尺
mov esi,Message //esi= Message
mov ecx,MSGLEN //ecx= MSGLEN
mov ebx,COLS //以下3行用于计算x的坐标位置
sub ebx,ecx
shr ebx,1
mov eax,24 //y=24
call WrtLn //写Message信息
call Show //把整个柱状图绘制信息打印出来
makefile文件内容:
vidbuff1: vidbuff1.o
ld -o vidbuff1 vidbuff1.o
vidbuff1.o: vidbuff1.asm
nasm -f elf -g -F stabs vidbuff1.asm
测试:
[root@bogon vidbuff1]# ./vidbuff1
12345678901234567890123456789012345678901234567890123456789012345678901234567890
_________
_________________
_______________________________________________________________________
____________________________________________________
_______________________________________________________
__________________
_____________________________
____________________________________
__________________
____________________________________________________________________
_____________________________________________________________________________
_______________________________________________________________
__________________________________________________________
____________________________________________
12345678901234567890123456789012345678901234567890123456789012345678901234567890
Data current as of 1/9/2015
圆柱条显示不出来,显示的是下划线,就凑合着看吧。