1. 程式人生 > >Linux命令列引數執行詳解

Linux命令列引數執行詳解

轉自: http://www.groad.net/bbs/simple/?t2609.html

1. Linux 如何從命令列執行程式
從 shell 中執行程式時,系統會為要執行的程式在記憶體中建立一個區域。分配給程式的記憶體區域可以位於實體記憶體的任何位置。為了使這一過程簡化,每個程式都被分配相同的虛擬記憶體地址。虛擬記憶體地址由作業系統對映到實體記憶體地址。

在 Linux 中,分配給程式的虛擬地址從地址 0x80480000 開始,到 0xbfffffff 結束。Linux 作業系統按照專門的格式把程式存放在虛擬記憶體地址中,如下圖所示:

記憶體區域中的第 1 塊區域包含會變程式碼的所有指令和資料(來自 .bss 和 .data 段)。指令步進包含彙編程式的指令程式碼,還包含 Linux 執行程式的連線過程所需要的指令資訊。

記憶體中的第 2 塊區域時堆疊區,它向下增長。但不能就認為,堆疊指標就是從 0xbfffffff 開始的。因為在載入程式之前,Linux 會把一些內容放到堆疊中,其中命令列引數

就在這裡。

2. 分析堆疊
程式啟動時,Linux 會將 4 種類型的資訊存放到程式堆疊中:

  • 命令列引數(包括程式名稱)的數目
  • 從 shell 裡執行的程式名稱
  • 命令列中包含的任何引數
  • 在程式啟動時所有當前的 Linux 環境變數
程式名稱、命令列引數和環境變數都是均已 '/' 結尾的長度可變的字串。為了讓工作變得更簡單,Linux 不僅把字串載入到堆疊中,還把執行每個這些元素的指標載入到堆疊中,所以可以很容易的在程式中定位它們。

程式啟動時,堆疊的一般佈局如下圖所示:

下面通過除錯 http://www.groad.net/bbs/read.php?tid-2600.html 中的程式觀察堆疊的情況。
引用 (gdb) b 13
Breakpoint 1 at 0x8048075: file area.s, line 13.
(gdb) run 10 20 30
Starting program
: /home/beyes/Program/Assembly/area 10 20 30

Breakpoint 1, _start () at area.s:13
13         finit
(gdb) print $esp
$1 = (void *) 0xbffff430

上面,Starting program 表示執行命令列中指定的命令列引數。這裡我們使用了 10, 20, 30 這 3 個數字作為命令列引數。地址 0xbffff430 是棧頂。現在看一下堆疊裡都如何存放上面所說的資料,使用 x 命令看記憶體中的值:
引用 (gdb) x/20x 0xbffff430
0xbffff430:    0x00000004    0xbffff5c9    0xbffff5eb    0xbffff5ee
0xbffff440:    0xbffff5f1    0x00000000    0xbffff5f4    0xbffff615
0xbffff450:    0xbffff628    0xbffff633    0xbffff643    0xbffff693
0xbffff460:    0xbffff6a5    0xbffff6cf    0xbffff6ef    0xbffff6fa
0xbffff470:    0xbffff71a    0xbffffbbb    0xbffffbe1    0xbffffc13

對照上圖:
第 1 個值 0x00000004 正是命令列的引數數目(包含全路徑的的程式名,10,20,30),共 4 個。
第 2 個值 0xbffff5c9 是包含全路經的程式名:
引用 (gdb) x/s 0xbffff5c9
0xbffff5c9:     "/home/beyes/Program/Assembly/area"

全路徑名+最後一個'/'字元共 0x22 個位元組, 0xbffff5c9 + 0x21 = 0xbffff5ea 。那麼從 0xbffff5eb 開始就存放命令列引數了。
第 3,4,5 個值分別是引數 10, 20, 30 的地址:
引用
(gdb) x/s 0xbffff5eb
0xbffff5eb:     "10"
(gdb) x/s 0xbffff5ee
0xbffff5ee:     "20"
(gdb) x/s 0xbffff5f1
0xbffff5f1:     "30"

注意,所有命令列引數都是以字串形式儲存的!如引數 "10" 的起始地址是 0xbffff5eb,結束地址為 0xbffffe3d,這裡總共 3 個位元組,其中包括 '/' 結尾這個位元組。這也就是為什麼檢視這個記憶體是是用 x/s 來顯示(s 表示顯示字串)。

在命令列引數之後,4 位元組的空值被放到堆疊中,作為引數和指向環境變數的指標的分界點。在 0x00000000 往上,是一些環境變數:
引用
(gdb) x/s 0xbffff5f4
0xbffff5f4:     "ORBIT_SOCKETDIR=/tmp/orbit-beyes"
(gdb) x/s 0xbffff615
0xbffff615:     "SSH_AGENT_PID=1359"
(gdb) x/s 0xbffff628
0xbffff628:     "TERM=xterm"
... ...

檢視命令列引數:
引用 .section .data
output1:
     .asciz "There are %d parameters:/n"
output2:
     .asciz "%s/n"

.section .text
.global _start
_start:
     movl (%esp), %ecx  #讀取"引數數目"
     pushl %ecx
     pushl $output1    
     call printf       #C函式的引數入棧從右到左入棧
     addl $4, %esp
     popl %ecx    
     movl %esp, %ebp    
     addl $4, %ebp    #EBP指向第一個命令列引數(即函式名./read)
loop1:
     pushl %ecx    #printf函式會改變ECX的值,這裡要入棧儲存起來
     pushl (%ebp)
     pushl $output2
     call printf
     addl $8, %esp
     popl %ecx    #彈出以遞減
     addl $4, %ebp
     loop loop1

     pushl $0
     call exit

執行與輸出:
引用 $ ./read 10 20 30
There are 4 parameters:
./read
10
20
30
檢視環境變數:
引用 .section .data
output:
     .asciz "%s/n"

.section .text
.global _start
_start:
     movl %esp, %ebp
     addl $12, %ebp   #指向環境變數(不加其他命令列引數執行程式)
loop1:
     cmpl $0, (%ebp)
     je endit
     pushl (%ebp)
     pushl $output
     call printf
     addl $12, %esp
     addl $4, %ebp
     loop loop1
endit:
     pushl $0
     call exit

執行與輸出:
引用
$ ./read2
ORBIT_SOCKETDIR=/tmp/orbit-beyes
SSH_AGENT_PID=1364
SHELL=/bin/bash
TERM=xterm
... ...