Linux命令列引數執行詳解
阿新 • • 發佈:2019-02-16
轉自: 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 環境變數
程式啟動時,堆疊的一般佈局如下圖所示:
下面通過除錯 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
... ...