1. 程式人生 > >彙編C語言程式,初探計算機執行原理

彙編C語言程式,初探計算機執行原理

張備 
 原創作品轉載請註明出處 《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 ”

一、知識儲備

1.馮·諾依曼體系結構

馮·諾依曼結構又稱作普林斯頓體系結構(Princetionarchitecture)。1945年,馮·諾依曼首先提出了“儲存程式”的概念和二進位制原理,後來,人們把利用這種概念和原理設計的電子計算機系統統稱為“馮·諾依曼型結構”計算機。馮·諾依曼結構的處理器使用同一個儲存器,經由同一個匯流排傳輸。 馮·諾依曼結構處理器具有以下幾個特點:必須有一個儲存器;必須有一個控制器;必須有一個運算器,用於完成算術運算和邏輯運算;

必須有輸入和輸出裝置,用於進行人機通訊。馮·諾依曼的主要貢獻就是提出並實現了“儲存程式”的概念。由於指令和資料都是二進位制碼,指令和運算元的地址又密切相關,因此,當初選擇這種結構是自然的。但是,這種指令和資料共享同一匯流排的結構,使得資訊流的傳輸成為限制計算機效能的瓶頸,影響了資料處理速度的提高。

2.32位彙編介紹

通過馮諾依曼體系我們不難得知,程式的執行程式碼儲存在儲存器中,由控制器控制程式碼的執行流程,將二進位制程式碼一步步變為程式的執行流程。由於二進位制程式碼編寫困難,可讀性性差,因此發明彙編程式碼代替二進位制程式碼。與高階語言不同,彙編程式碼實質上與二進位制程式碼無本質區別。

二進位制編碼一般由操作碼和地址碼

兩部分組成,地址碼一般分為雙地址(mov ax,bx),單地址(push ax),和零地址(ret),我們以8位元組長的暫存器-暫存器變址定址為例,一般設計格式如下:

2

3

3

操作碼OP

源暫存器R1

目的暫存器R2

在這種設計下,我們支援4種不同的雙位元組操作碼,最多支援8個不同的暫存器,若mov由01表示,暫存器ax為001、暫存器bx為010,則可以得知二進位制碼01001010代表mov ax , bx ;

若想加入地址編碼,則可以如下設計:

5 3
操作碼OP
源暫存器R1
  

為了區分單地址和雙地址,可以讓其採操作碼擁有不同的字首碼,如讓00,01,10開頭的為雙位元組碼;而以11開頭的為單位元組碼,由於單地址碼操作碼支援5位,因此實質上系統支援的單地址操作碼有8個。即系統支援三種不同的雙位元組指令,八種不同的單位元組指令,以及八種不同的暫存器進行定址。按照這種方式還可以繼續擴充出零地址指令,我們可以設計不同的操作碼結構來支援系統多樣的指令結構。

以上述兩個設計為例分析二進位制碼:

(1)01001011 由於是01開頭,為雙地址操作碼 則劃分為 01 001 011 則操作是01號操作,兩個運算元分別為001和011;

(2)11001011 由於是11開頭,為單地址操作碼 則劃分為 11001 011  則操作是11001號操作,運算元為011;

我們可以這樣設計彙編碼:操作01代表mov、操作11001代表push、地址001代表ax、地址011代表dx;

    則(1)即是mov ax,dx;

    (2)即是push dx;

當然我們也可以16位設計變址定址方式的二進位制碼,如

4

3

1

8

操作碼OP

通用暫存器

變址暫存器

偏移地址

實質上彙編程式碼是二進位制程式碼的一種轉義,彙編碼和二進位制碼結構上高度一致,彙編碼的構成也和二進位制碼相同,分為操作碼和地址碼,像如下設計

因此通過分析一段程式碼的彙編碼,就可以得知一段程式的執行流程,而且其大體上就是計算機在底層中操作的具體過程,x86_32位作業系統支援的令常見的如下作者有詳細介紹,僅摘抄出本文可能設計到的部分:

movl	%esp, %ebp;     將esp的內容傳輸到ebp中
addl	$3, %eax;       將eax+3 
pushl	%ebp;           將esp的內容壓入棧中
popl	%ebp;           將棧頂的值彈出放入ebp中
leave;                  清除棧空間
call f;                 呼叫f函式
ret;                    呼叫返回

二、C語言原始碼簡單分析

1、簡單C語言程式碼

int g(int x)
{
  return x + 3;
}

int f(int x)
{
  return g(x);
}

int main(void)
{
  return f(8) + 1;
}

2、將C語言編譯為彙編碼

在Linux下 使用命令

gcc -o Fb.s Fb.c -S -m32 

將Fb.c編譯為32位彙編碼Fb.s

由於彙編碼中有大量以 . 開頭的輔助程式碼,這裡我們分析時並不需要

使用命令

egrep  "^\s*[a-zA-Z]" Fb.s > grep.S

利用正則表示式將不必要的資訊刪除


g:
	pushl	%ebp;			將ebp的內容壓入棧中
	movl	%esp, %ebp;		將esp的內容傳輸到ebp中
	movl	8(%ebp), %eax;  將epb+8 所指向的內容傳給eax
	addl	$3, %eax;		將eax+3
	popl	%ebp;			將棧頂的值彈出放入ebp中
	ret;					呼叫返回
f:
	pushl	%ebp;           將ebp的內容壓入棧中
	movl	%esp, %ebp;     將esp的內容傳輸到ebp中
	pushl	8(%ebp);        將epb+8 壓棧
	call	g;              呼叫g函式
	addl	$4, %esp;       令esp=esp+4
	leave;	
	ret;	                返回
main:
	pushl	%ebp;           將ebp的內容壓入棧中
    movl	%esp, %ebp;     將esp的內容傳輸到ebp中
	pushl	$8;             將8壓入棧中
	call	f;				呼叫f函式
	addl	$4, %esp;       令esp=esp+1
	addl	$1, %eax;       eax++
	leave;
	ret;
empty

3、理解彙編程式碼的執行過程


4、分析總結

計算機工作,實質上就是從指令中按照順序一步步執行指令,每一條指令包含操作和運算元,運算元一般支援多種定址方式,包括直接定址、暫存器定址、立即定址、相對定址、基址定址、變址定址等不同定址方式、輔助支援現代計算機基於儲存的程式碼執行方式。
操作包含常見的運算操作,類如加減乘除、移位、模除、取反等常見運算指令,但是對於控制程式碼執行來說,更重要的是控制執行流程的指令,如JZ,JNZ,JMP等條件跳轉指令,支援符合某一條件下跳轉到制定程式碼。支援函式呼叫Call和函式返回ret指令,利用堆疊儲存呼叫函式時的堆疊和指令資訊,能夠快速的切換呼叫子函式的上下文。