哈工大CSAPP大做業

第1章 概述

1.1 Hello簡介

hello的源碼hello.c文件,要生成可執行文件,首先要進行預處理,其次要進行編譯生成彙編代碼,接着進行彙編處理生成目標文件,目標文件經過連接器造成一個可執行文件,可執行文件須要一個執行環境,它能夠在linux下經過shell進行運行,與計算機其餘常常文件同步運行,並經過異常處理機制相應信號。在運行的過程當中,程序經過Intel內存管理機制一步步訪問邏輯地址、虛擬地址、物理地址,從而進行數據交換,還能夠經過IO機制進行輸入輸出交互

1.2 環境與工具

環境:ubuntu64位 vmware虛擬機環境中運行html

gdblinux

edbshell

ida pro編程

visual studioubuntu

hexedit數組

notepad++緩存

objdumpbash

1.3 中間結果

hello.c源程序app

hello.i預處理後的源程序異步

hello.s彙編代碼

hello.o目標程序

hello可執行文件

elfo.txt連接前的elf文件信息

elfe.txt連接後的elf文件信息

asmo.txt hello.o反編譯結果

asme.txt hello反編譯結果

1.4 本章小結

題太多了,太累了QAQ

 

(第1章0.5分)

 

 


第2章 預處理

2.1 預處理的概念與做用

概念:在編譯以前進行的處理。

做用:1.宏定義2.文件包含3.條件編譯

 

2.2在Ubuntu下預處理的命令

gcc hello.c -E

 

 

2.3 Hello的預處理結果解析

hello.c程序中只包含三條預處理指令

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

做用是文件包含,即包含stdio.h,unistd.h,stdlib.h三個文件

經過-E指令,只激活預處理指令,能夠看到執行完這三條文件包含指令的結果:將三個文件的內容引入,代碼量提高至約800行(詳見hello.i)

 

2.4 本章小結

預處理命令,能夠在編譯器編譯以前,提早進行一些操做,好比定義常量,還能夠進行條件編譯以方便調試,能夠進行文件引入來導入一些預先寫好的模塊,便於程序的組織和調試和一些特殊的編程技巧的實現,是一項很是有用的功能。

 

(第2章0.5分)


第3章 編譯

3.1 編譯的概念與做用

 

編譯(compilation , compile)

利用編譯程序從源語言編寫的源程序產生目標程序的過程。

做用:

把用高級程序設計語言書寫的源程序,翻譯成等價的計算機彙編語言

 

3.2 在Ubuntu下編譯的命令

gcc hello.i -S

 

3.3 Hello的編譯結果解析

1.int sleepsecs = 2.5

該句定義一個int類型全局變量sleepsecs,其值爲2(向整數向下取整後的結果),在僞彙編文件中,先定義一個全局符號sleepsecs,用於標識和鏈接。

       .globl     sleepsecs

在.data指令後,描述一些該變量的詳細信息

       .type      sleepsecs, @object 將sleepsecs定義爲對象類型

       .size       sleepsecs, 4 佔用4個字節

sleepsecs: 定義sleepsecs對應的標籤

       .long      2 定義爲長整型數2

      

2.main函數

       .text 在text節中定義

       .globl     main 聲明全局符號global

       .type      main, @function 將main聲明爲函數

隨後定義main:標籤,後跟main函數僞彙編指令

 

3.int i

這是一個未初始化的局部變量,它存放在棧中,位置是-4(%rbp)

4.if(argc!=3)

       cmpl      $3, -20(%rbp)

       je    .L2

 

argc做爲main的參數,存放在-20(%rbp)的位置,將它與3比較,若是相等就不執行後面大括號的部分

5.printf("Usage: Hello 學號 姓名!\n");

       movl      $.LC0, %edi

       call puts

將.LC0的部分傳入參數,執行printf

.LC0定義在.rodata節中,是一個字符常量

.LC0:

       .string       "Usage:Hello\345\255\246\345\217\267\345\247\223\345\220\215\357\274\201"

6.exit(1);

       movl      $1, %edi

       call exit

參數爲1,執行exit函數

7.for(i=0;i<10;i++)

.L2:

       movl      $0, -4(%rbp)

       jmp .L3

該節初始化i爲0跳轉到判斷節

.L3:

       cmpl      $9, -4(%rbp)

       jle   .L4

若是知足條件就跳轉、繼續執行

       addl $1, -4(%rbp)

循環變量遞增

8. printf("Hello %s %s\n",argv[1],argv[2]);

       movq     -32(%rbp), %rax

       addq      $16, %rax

       movq     (%rax), %rdx

將argv[2]傳給%rdx

       movq     -32(%rbp), %rax

       addq      $8, %rax

       movq     (%rax), %rax

       movq     %rax, %rsi

將argv[1]傳給%rsi

       movl      $.LC1, %edi

將字符串常量"Hello %s %s\n"傳給%edi

       movl      $0, %eax

將%eax賦0

       call printf

執行printf

如下格式自行編排,編輯時刪除

9.sleep(sleepsecs);

       movl      sleepsecs(%rip), %eax

       movl      %eax, %edi

       call sleep

將sleep傳給%edi做爲參數,執行sleep函數

10. getchar()

       call getchar

執行getchar函數

 

11. return 0;

leave

釋放堆棧空間

       ret

返回

3.4 本章小結

本節涉及到的指令所有爲gun彙編程序(gas)的僞彙編指令,相比最後的彙編指令內容更爲精簡,方便閱讀、分析。程序將常量放入.rodata節,初始化全局變量放入.data節,經過標籤訂義和跳轉等方式定義許多操做,爲後序的彙編和連接生成可執行文件準備。

(第32分)


第4章 彙編

4.1 彙編的概念與做用

概念:

把彙編語言翻譯成機器語言的過程稱爲彙編

 

做用:

將彙編語言翻譯成機器語言

 

4.2 在Ubuntu下彙編的命令

       gcc -c hello.s

 

 

4.3 可重定位目標elf格式

分析hello.o的ELF格式,用readelf等列出其各節的基本信息,特別是重定位項目分析。

 

 

各Section基本信息:

 

Name:名稱

Type:類型

Address:地址

Offset:地址偏移量

Size:大小

EntSize:全體大小

Flag:旗標

Link:被重定位的符號所在的符號表的section index

Info:須要被重定位的section的index

Align:對齊信息

包含重定位信息的節

 

offset表示該符號在被重定位的section中的偏移

info的高4個字節表示該符號在.symtab中的index,低4字節表示重定位的類型

type表示符號類型

sym.value表示連接過程當中將要寫入地址的位置

sym.name表示符號名稱

append追加地址

 

4.4 Hello.o的結果解析

機器語言是直接用二進制代碼指令表達的計算機語言,指令是用0和1組成的一串代碼,它們有必定的位數,並分紅若干段,各段的編碼表示不一樣的含義。

每一條彙編語句被映射爲若干二進制指令碼,將機器語言的每一條指令符號化:指令碼代之以記憶符號,地址碼代之以符號地址。

在彙編語言中,操做數用十進制表示,而在機器語言中,用十六進制表示,如hello.o中:

機器語言中命令

   4:    48 83 ec 20             sub    $0x20,%rsp

在彙編語言hello.s中對應爲

      subq      $32, %rsp

 

objdump -d -r hello.o  分析hello.o的反彙編,並請與第3章的 hello.s進行對照分析。

在彙編語言中,分支跳轉、函數調用,使用標籤訂位位置,而在機器語言中使用地址+偏移量計算要跳轉到的實際地址。

如hello.o中:

  6f:    7e c1                jle    32 <main+0x32>

  71:    e8 00 00 00 00           callq  76 <main+0x76>

在hello.s中對應爲:

      jle   .L4

      call getchar

4.5 本章小結

彙編是將計算機不能讀懂的彙編語言翻譯成計算機能讀懂的機器語言的不可缺乏的重要步驟.

(第41分)

_
第5章 連接

5.1 連接的概念與做用

概念:

連接是將各類代碼和數據片斷收集並組合成一個單一文件的過程,這個文件可被加載(複製)到內存並執行。

做用:

連接在軟件開發中扮演着一個關鍵的角色,由於它使分離編譯成爲可能。

5.2 在Ubuntu下連接的命令

ld -o hello -dynamic-linker /lib/ld-linker.so.2 /usr/lib/crt1.o /usr/lib/crti.o -l hello.o /usr/lib/ctrn.o

 

5.3 可執行目標文件hello的格式

 

 

offset:偏移量

virtaddr:虛擬地址

phyaddr:物理地址

filesiz:文件中的大小

memsiz:內存中的大小

flags:旗標

align:對齊

 

5.4 hello的虛擬地址空間

   

 

實際運行中,全部虛擬地址空間段大小都爲0x1000,且一段中包含一個或多個5.3中的程序段。動態連接庫中的文件映射到內存的內容,與hello文件中映射到內存的內容地址間隔較大。程序中還包括[stack],[vvar],[vdso],[vsyscall]等特殊用途的地址段。

5.5 連接的重定位過程分析

 

 

不一樣:

hello中包含一些外部文件的宏定義、變量、庫函數和操做系統的啓動代碼等,且.o文件.text節從0開始,而可執行文件.text節並不是從0開始。

過程:分爲符號解析和重定位兩步

  1. 符號解析:目標文件定義和引用符號,每一個符號對應於一個函數、一個全局變量或一個靜態變量。符號解析的目的是將每一個符號引用正好和一個符號定義關聯起來
  2. 重定位:編譯器和彙編器生成從地址0開始的代碼和數據節。連接器經過把每一個符號定義與一個內存位置關聯起來,從而重定位這些節,而後修改全部對這些符號的引用,使得它們指向這個內存位置。連接器使用匯編器產生的重定位條目的詳細指令,不加甄別地執行這樣的重定位。

重定位:

hello.o的文件中包含一些重定位條目

 

這些重定位條目告訴連接器32位PC相對地址或32位絕對地址進行重定位,這些重定位條目經過計算地址或直接調用保存的絕對地址,達到重定位的目的。

不管什麼時候彙編器遇到對最終位置未知的目標引用,會生成一個重定位條目,告訴連接器在將目標文件合併成可執行文件時如何修改這個引用。代碼的重定位條目放在.rel.text中,已初始化數據的條目放在.rel.data中

             

5.6 hello的執行流程

_start

__libc_start_main

__GI___cxa_atexit

__internal_atexit

__GI___cxa_atexit

__internal_atexit

__new_exitfn

__internal_atexit

__GI___cxa_atexit

__libc_start_main

_setjmp

__sigsetjmp

__sigjmp_save

__libc_start_main

__GI_exit

__run_exit_handlers

__GI___call_tls_dtors

__run_exit_handlers

__do_global_dtors_aux

deregister_tm_clones

__do_global_dtors_aux

_fini

__run_exit_handlers

_IO_cleanup

_IO_unbuffer_all

_IO_cleanup

_IO_flush_all_lockp

_IO_cleanup

_IO_unbuffer_all

_IO_cleanup

_IO_unbuffer_all

_IO_new_file_setbuf

_IO_default_setbuf

_IO_new_file_sync

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

_IO_new_file_setbuf

_IO_unbuffer_all

_IO_new_file_setbuf

_IO_default_setbuf

_IO_new_file_sync

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

_IO_new_file_setbuf

_IO_unbuffer_all

_IO_cleanup

__run_exit_handlers

__GI__exit

5.7 Hello的動態連接分析

動態連接項目以下圖(主要爲兩個.so文件相關內容)

  

 

分析GOT的變化

dl_init前:

 

dl_init後

 

0x6010208~0x601020d字節發生了變化

5.8 本章小結

(我的認爲,在全書中,「連接」這一章的難度比全部其餘章節之和還要大,搞清楚動態連接的全過程十分困難,課本上也有意的跳過了一些部分,致使一些細節問題十分費解。)

連接是組建大型程序和團隊編程不可缺乏的重要部分,掌握連接器的一些原理和動態連接是很是有必要的,也是學習庫打樁等強大機制的基礎。雖然hello.c很簡單,可是也須要和標準庫進行連接。瞭解hello.c連接的前因後果,對掌握連接技術頗有幫助。

(第51分)

 


第6章 hello進程管理

6.1 進程的概念與做用

概念:

進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。

做用:

因爲程序是靜態的,咱們看到的程序是存儲在存儲介質上的,它沒法反映出程序執行過程當中的動態特性,並且程序在執行過程當中是不斷申請資源,程序做爲共享資源的基本單位是不合適的,因此須要引入一個概念,它能描述程序的執行過程並且能夠做爲共享資源的基本單位,這個概念就是進程。進程解決了系統資源調度等一系列問題。

6.2 簡述殼Shell-bash的做用與處理流程

Shell俗稱殼(用來區別於內核),是指「提供使用者使用界面」的軟件,就是一個命令行解釋器。

bash 是一個爲GNU項目編寫的Unix shell,也就是linux用的shell。

因此Shell-bash的「做用」是linux系統的一個命令行解釋器

 

 

 

 

流程:

 

 

6.3 Hello的fork進程建立過程

shell調用fork函數,造成自身的一個拷貝(子進程),爲運行hello作準備

6.4 Hello的execve過程

在shell的子進程中執行execve函數,將參數傳給Hello程序,並執行Hello

6.5 Hello的進程執行

一開始,Hello運行在用戶模式,當程序收到一個信號時,進入內核模式,運行信號處理程序,以後再返回用戶模式。在Hello運行的過程當中,cpu不斷切換上下文,使Hello程序運行過程被切分紅時間片,與其餘進程交替佔用cpu,實現進程的調度。

6.6 hello的異常與信號處理

1.輸入Ctrl-C時,程序終止

 

 

處理過程是向程序發送SIGINT信號,程序執行默認行爲:中止執行

 

2.輸入Ctrl-C時,程序掛起

      

 

處理過程是向程序發送SIGSTP信號,程序執行默認行爲:掛起程序,以後會返回shell中

 

  1. 亂按+回車

 

 

輸入的內容會被留在緩衝區中,當hello執行結束,返回shell中,shell會從緩衝區讀取並嘗試解析這些內容

  1. ps jobs pstree fg kill命令

 

 

ps:顯示當前進程的狀態

jobs:查看後臺運行的進程

fg:恢復一個後臺進程

pstree:顯示進程樹

kill:結束一個進程

 

可能會產生IO中斷、時鐘中斷、系統調用等等,會產生SIGINT、SIGSTP等信號。

6.7本章小結

linux命令行shell是一個很是強大的工具,用它能夠更方便的執行Hello和發送各類命令請求。經過信號等方式能夠實現異常處理,讓Hello在順序執行者也能處理一些突發情況和實現一些功能。進程調度實現了各個進程計算資源合理分配,互不干擾,提升了系統穩定性和效率。

(第61分)


第7章 hello的存儲管理

7.1 hello的存儲器地址空間

物理地址(physical address)

用於內存芯片級的單元尋址,與處理器和CPU鏈接的地址總線相對應。

 

邏輯地址(logical address)

邏輯地址指的是機器語言指令中,用來指定一個操做數或者是一條指令的地址。如Hello中sleepsecs這個操做數的地址。

 

線性地址(linear address)或也叫虛擬地址(virtual address)

跟邏輯地址相似,它也是一個不真實的地址,若是邏輯地址是對應的硬件平臺段式管理轉換前地址的話,那麼線性地址則對應了硬件頁式內存的轉換前地址。

 

7.2 Intel邏輯地址到線性地址的變換-段式管理

  段式內存管理方式就是直接將邏輯地址轉換成物理地址,也就是CPU不支持分頁機制。其地址的基本組成方式是段號+段內偏移地址。

在x86保護模式下,段的信息(段基線性地址、長度、權限等)即段描述符佔8個字節,段信息沒法直接存放在段寄存器中(段寄存器只有2字節)。Intel的設計是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT內的索引值(index)。

首先給定一個完整的邏輯地址[段選擇符:段內偏移地址],

      1.看段選擇描述符中的T1字段是0仍是1,能夠知道當前要轉換的是GDT中的段,仍是LDT中的段,再根據指定的相應的寄存器,獲得其地址和大小,咱們就有了一個數組了。

      2.拿出段選擇符中的前13位,能夠在這個數組中查找到對應的段描述符,這樣就有了Base,即基地址就知道了。

      3.把基地址Base+Offset,就是要轉換的下一個階段的地址。

7.3 Hello的線性地址到物理地址的變換-頁式管理

分頁的基本原理是把內存劃分紅大小固定的若干單元,每一個單元稱爲一頁(page),每頁包含4k字節的地址空間(爲簡化分析,咱們不考慮擴展分頁的狀況)。這樣每一頁的起始地址都是4k字節對齊的。爲了能轉換成物理地址,咱們須要給CPU提供當前任務的線性地址轉物理地址的查找表,即頁表(page table)。

       爲了節約頁表佔用的內存空間,x86將線性地址經過頁目錄表和頁表兩級查找轉換成物理地址。32位的線性地址被分紅3個部分:最高10位 Directory 頁目錄表偏移量,中間10位 Table是頁表偏移量,最低12位Offset是物理頁內的字節偏移量。頁目錄表的大小爲4k(恰好是一個頁的大小),包含1024項,每一個項4字節(32位),項目裏存儲的內容就是頁表的物理地址。若是頁目錄表中的頁表還沒有分配,則物理地址填0。頁表的大小也是4k,一樣包含1024項,每一個項4字節,內容爲最終物理頁的物理內存起始地址。

      

       每一個活動的任務,必需要先分配給它一個頁目錄表,並把頁目錄表的物理地址存入cr3寄存器。頁表能夠提早分配好,也能夠在用到的時候再分配。

7.4 TLB與四級頁表支持下的VA到PA的變換

 

7.5 三級Cache支持下的物理內存訪問

先訪問一級緩存,不命中時訪問二級緩存,再不命中訪問三級緩存,再不命中訪問主存,若是主存缺頁則訪問硬盤

 

7.6 hello進程fork時的內存映射

執行新進程(hello)時,爲這個新進程建立虛擬內存

  1. 建立當前進程的的mm_struct, vm_area_struct和頁表的原樣副本
  2. 兩個進程中的每一個頁面都標記爲只讀
  3. 兩個進程中的每一個區域結構(vm_area_struct) 都標記爲私有的寫時複製

在新進程中返回時,新進程擁有與調用fork進程相同的虛擬內存, 隨後的寫操做經過寫時複製機制建立新頁面

7.7 hello進程execve時的內存映射

 

  1. 刪除已存在的用戶區域
  2. 建立新的區域結構: 代碼和初始化數據映射到.text和.data區(目標文件提供), .bss和棧映射到匿名文件
  3. 設置PC,指向代碼區域的入口點

7.8 缺頁故障與缺頁中斷處理

如下格式自行編排,編輯時刪除

缺頁故障:須要訪問的頁不在主存,須要操做系統將其調入後才能訪問。

有三種狀況:

 

只有正常缺頁時,系統纔會調入須要訪問的頁,並再次執行訪問該頁的命令。

7.9動態存儲分配管理

基本方法:維護一個虛擬內存區域「堆」,將堆視爲一組不一樣大小的 塊(blocks)的集合來維護,每一個塊要麼是已分配的,要麼是空閒的,須要時選擇一個合適的內存塊進行分配。

  1. 記錄空閒塊,能夠選擇隱式空閒鏈表,顯示空閒鏈表,分離的空閒鏈表和按塊大小排序創建平衡樹
  2. 放置策略,能夠選擇首次適配,下一次適配,最佳適配
  3. 合併策略,能夠選擇當即合併,延遲合併
  4. 須要考慮分割空閒塊的時機,對內部碎片的忍耐閾值.

7.10本章小結

經過高速緩存、虛擬內存、動態內存分配,能夠實現快速、高校、利用率高的儲存空間管理。能夠經過內存映射等方式實現文件共享。儲存管理是一個至關重要、值得研究的機制。

(第7 2分)


第8章 hello的IO管理

8.1 Linux的IO設備管理方法

設備的模型化:將設備抽象成文件

設備管理:經過unix io接口管理

8.2 簡述Unix IO接口及其函數

打開和關閉文件:open()and close()
讀寫文件:read() and write()
改變當前的文件位置 lseek()

 8.3 printf的實現分析

1.從vsprintf生成顯示信息到write系統函數,到陷阱-系統調用 int 0x80或syscall.

2.字符顯示驅動子程序:從ASCII到字模庫到顯示vram(存儲每個點的RGB顏色信息)。

3.顯示芯片按照刷新頻率逐行讀取vram,並經過信號線向液晶顯示器傳輸每個點(RGB份量)。

8.4 getchar的實現分析

1.異步異常-鍵盤中斷的處理:鍵盤中斷處理子程序。接受按鍵掃描碼轉成ascii碼,保存到系統的鍵盤緩衝區。

2.getchar等調用read系統函數,經過系統調用讀取按鍵ascii碼,直到接受到回車鍵才返回。

8.5本章小結

輸入輸出看似簡單,實際是一個很是精巧的過程,從程序發出請求到系統函數調用到設備相應,須要執行許多步驟,每每也是拖慢程序的主要因素和一些崩潰異常的高發地,須要謹慎選用函數、命令實現目的。

(第81分)

結論

hello的源碼hello.c文件,要生成可執行文件,首先要進行預處理,其次要進行編譯生成彙編代碼,接着進行彙編處理生成目標文件,目標文件經過連接器造成一個可執行文件,可執行文件須要一個執行環境,它能夠在linux下經過shell進行運行,與計算機其餘常常文件同步運行,並經過異常處理機制相應信號。在運行的過程當中,程序經過Intel內存管理機制一步步訪問邏輯地址、虛擬地址、物理地址,從而進行數據交換,還能夠經過IO機制進行輸入輸出交互。

經過學習ics這門課程,深感計算機這個龐大致系的複雜、精巧,從電路到電路組合,再到硬件集成、軟件調配,每一處都層次分明、隨處顯現着前人的智慧。很多複雜概念的學習,經過這門課感受僅僅只是入了個門,距離熟練運用甚至涉及差距仍然較大,但不妨礙進行一些思惟上的創新。

目前的電子計算機創建在二進制基礎上,未來基礎物理突破以後,可能會實現三進制、四進制的計算機,沒準能夠實現質的飛越。或許神經科學有了巨大飛越,讓「電腦」真正構建起一個相似於人腦的神經結構,實現一個強的人工智能。

參考文獻

爲完成本次大做業你翻閱的書籍與網站等

 [1]《深刻理解計算機系統》

 [2] 哈工大計算機系統課程講課ppt

 [3] [轉]printf 函數實現的深刻剖析http://www.javashuo.com/article/p-fbeaelqg-y.html

 [4] 【不周山之讀薄 CSAPP】肆 連接 https://wdxtub.com/2016/04/16/thin-csapp-4/

 [5] Linux下庫函數動態連接過程分析-結合glibc-2.11源碼 https://blog.csdn.net/lzshlzsh/article/details/6066628

 [6] Linux下 可視化 反彙編工具 EDB 基本操做知識 http://www.javashuo.com/article/p-fpntsgri-gm.html

 [7] gdb基本命令(很是詳細)

https://blog.csdn.net/z15818264727/article/details/69668820

[8] Linux中查看進程的虛擬地址空間內存佈局 https://blog.csdn.net/ASJBFJSB/article/details/81429576

[9] GNU ARM 彙編僞指令(Assembler Directives) https://blog.csdn.net/liuzq/article/details/83615085

[10] GCC經常使用參數詳解 https://www.cnblogs.com/zhangsir6/articles/2956798.html

相關文章
相關標籤/搜索