linux c小程序下载,C入门小程序

C入门小程序

这篇文档主要设计以下几个知识点:

C语言的指针

gcc和gdb的简单使用

简单的汇编指令

Linux环境下C程序运行时的栈帧

奇怪的代码

注意 除非在使用gcc编译代码的时候说明使用了编译器优化参数-O,其它所有的gcc命令都不使用编译器优化。

刚入门写下下面的代码很常见,虽然是错误的写法(ch指针没有初始化),但是程序的却能正常的执行。

#include

int main() {

long i;

long j;

char *ch;

scanf("%s", ch);

}

但是在scanf语句下在给变量i和j进行初始化话,程序却不能正确的执行了,在执行scanf后会抛出SegmentFault异常。

#include

int main() {

long i;

long j;

char *ch;

scanf("%s", ch);

i = 0;

j = 0;

}

更奇怪的是,如果将i或者j注释掉其中的一个,程序又能正常的执行:

#include

int main() {

long i;

long j;

char *ch;

scanf("%s", ch);

i = 0;

//j = 0;

}

DEBUG

为了搞明白为什么会出现这种情况,需要使用gdb来调试这两段代码。

gcc -O0 -g main.c -o main.o

gdb main.o

进入gdb调试界面,首选使用disassemble来反汇编函数main,这样可以帮助设置断点和查看编译后的汇编指令。

注释掉j的gdb调试界面,版本一:

(gdb) disassemble main

Dump of assembler code for function main:

0x0000000000400546 : push %rbp

0x0000000000400547 : mov %rsp,%rbp

0x000000000040054a : sub $0x10,%rsp

0x000000000040054e : mov -0x10(%rbp),%rax

0x0000000000400552 : mov %rax,%rsi

0x0000000000400555 : mov $0x400604,%edi

0x000000000040055a : mov $0x0,%eax

0x000000000040055f : callq 0x400430 <__isoc99_scanf>

0x0000000000400564 : movq $0x0,-0x8(%rbp)

0x000000000040056c : mov $0x0,%eax

0x0000000000400571 : leaveq

0x0000000000400572 : retq

没有注释掉i和j的gdb调试界面,版本二:

(gdb) disassemble main

Dump of assembler code for function main:

0x0000000000400546 : push %rbp

0x0000000000400547 : mov %rsp,%rbp

0x000000000040054a : sub $0x20,%rsp

0x000000000040054e : mov -0x18(%rbp),%rax

0x0000000000400552 : mov %rax,%rsi

0x0000000000400555 : mov $0x400604,%edi

0x000000000040055a : mov $0x0,%eax

0x000000000040055f : callq 0x400430 <__isoc99_scanf>

0x0000000000400564 : movq $0x0,-0x10(%rbp)

0x000000000040056c : movq $0x0,-0x8(%rbp)

0x0000000000400574 : mov $0x0,%eax

0x0000000000400579 : leaveq

0x000000000040057a : retq

观察两段代码的汇编指令。只有在0x000000000040054e处代码不一样:

mov -0x10(%rbp),%rax 表示将地址%rbp-0x10处的值传递到%rax寄存器

mov -0x18(%rbp),%rax 表示将地址%rbp-0x18处的值传递到%rax寄存器

然后在执行mov %rax %rsi,表示将寄存器$rxa的值传递给寄存器%rsi,%rsi表示函数调用时的第二个参数,当调用scanf("%s", ch)的时候,ch的值就是%rsi的值。既然程序执行的时候在scanf报错,那么源头就是%rsi的值不一样。那么就继续回到gdb界面,在调用scanf的之前设置断点(这里选择地址0x0000000000400552处),观察一下%rsi的值到底是什么。

首选设置断点,然后执行程序:

(gdb) b *0x000000000040055f

(gdb) run

然后执行info register rsi指令,来打印$rsi的值:

rsi 0x7fffffffddf0 140737488346608

rsi 0x400450 4195408

程序执行的结果表面,地址0x7fffffffddf0是合法的,0x400450是非法的。在执行0x000000000040055f(scanf("%s", ch))的时候两个版本会将标准输入分别写入以地址0x7fffffffddf0和0x400450为始的连续内存空间上。

为什么地址0x400450是非法的?来看一下Linux x86-64运行时的内存镜像:

bV6KWu?w=2040&h=1882

地址0x400450在Read-only code segment区域,所以对这块地址进行写操作是非法的。在运行时环境中,只能对stack和heap进行写操作。

为什么rsi地址在两个版本下的值差别这么大,一个在高地址内存空间,而另一个在低地址内存空间?在执行main函数之前,运行时已经在栈上进行了入栈出栈的操作了,地址-0x10(%rbp)和-0x18(%rbp)的值是上一次入栈时写入的数据,因为没有进行初始化,所以还保留了上一次操作的值(个人臆测)。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值