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
... ...


beyes2010-08-20 23:26
在堆栈中存放的命令行参数是以字符串的形式来查看的,如果是数字型参数,要对其转换后才可以使用。转换可以使用 C 库函数:
  • atoi() : 把 ASCII 字符串转换为短整数值
  • atol() : 把 ASCII 字符串转换为长整数值
  • atof() : 把 ASCII 字符串转换为双精度浮点值
调用这些函数前,指向参数字符串位置的指针必须放在堆栈中。atoi() 的结果返回到 EAX 中,atol() 的结果存放在 EDX:EAX中,atof() 结果返回到 FPU 的 ST(0) 寄存器中。

测试程序:
引用
.section .data
output:
     .asciz "This area is: %f/n"

.section .bss
     .lcomm result , 4

.section .text
.global _start

_start:
     nop
     finit
    
     pushl 8( %esp)     #取得命令行中的半径值(字符串地址)
     call atoi
     addl $4 , %esp     #恢复堆栈
     movl %eax , result
     fldpi
     filds result
     fmul %st( 0 ), %st( 0)     #半径平方
     fmul %st( 1 ), %st( 0)     #与pi相乘
     fstpl ( %esp)
     pushl $output
     call printf
     addl $12 , %esp

     pushl $0
     call exit

运行与输出:
引用
$ ./cmd 10
This area is: 314.159265

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值