当前位置: 首页 > Linux

C入门小程序

时间:2023-04-06 02:43:36 Linux

C入门小程序本文档主要设计以下知识点:-O在使用gcc编译代码时指定,所有其他gcc命令不使用编译器优化。一开始写下面的代码是很常见的。虽然是错误的(ch指针没有初始化),但是程序可以正常执行。#includeintmain(){longi;长j;字符*ch;scanf("%s",ch);}但是,如果在scanf语句下初始化变量i和j,程序就不能正确执行,执行scanf后会抛出SegmentFault异常。#includeintmain(){longi;长j;字符*ch;scanf("%s",ch);我=0;j=0;}更奇怪的是,如果将i或j中的一个注释掉,程序可以正常执行:#includeintmain(){longi;长j;字符*ch;scanf("%s",ch);我=0;//j=0;}DEBUG为了理解为什么会出现这种情况,需要使用gdb调试这两段代码。gcc-O0-gmain.c-omain.ogdbmain.o进入gdb调试界面,首选使用disassemble对main函数进行反汇编,可以帮助设置断点和查看编译好的汇编指令。注释掉j版本1的gdb调试接口:(gdb)disassemblemainDumpofassemblercodeforfunctionmain:0x0000000000400546<+0>:push%rbp0x0000000000400547<+1>:mov%rsp,%rbp0x0000000000400004a<4005:4aSUB$0x10,%RSP0x000000000040054e<+8>:MOV-0X10(%RBP),%RAX0x0000000000400552<+12>:mov%Rax,%RSI0x0000000000555<+15>:MOV$0x400604,%EDI5050505005005005005005005+20055a<+20055a+20055a+20055a。>:MOV$0x0,%Eax0x000000000040055F<+25>:Callq0x400430<__isoc99_scanf@PLT>0x00000000400564<+30>:MOVQ$0x0,-0x8(%RBP)0x0000000040056V$<+38>,%MOV+43>:leaveq0x0000000000400572<+44>:retq没有注释掉i和j的gdb调试接口,版本2:(gdb)反汇编函数main的汇编代码的mainDump:0x0000000000400546<+0>:push%rbp0x0000400007<+0004+1>:mov%rsp,%rbp0x000000000040054A<+4>:sub$0x20,%rsp0x000000000040040054e<+8>:mov-0x18(%rbp),%rax0x0000000000400400552<+12><+20>:mov$0x0,%eax0x000000000040055f<+25>:callq0x400430<__isoc99_scanf@plt>0x0000000000400564<+30>:movq$0x0,-0x10(%rbp)0x00000000000mov+0,0,6-0x8(%rbp)0x0000000000400574<+46>:mov$0x0,%eax0x0000000000400579<+51>:leaveq0x000000000040057a<+52>:retq观察两个代码的汇编指令仅在0x500e40x10(mov-0x10(mov-0x10)%rbp),%rax表示将地址%rbp-0x10的值传送到%rax寄存器mov-0x18(%rbp),%rax表示将地址%rbp-0x18的值传送到%rax寄存器然后执行mov%rax%rsi,表示将寄存器$rxa的值传递给寄存器%rsi,%rsi表示调用函数时的第二个参数,调用scanf("%s",ch)时,ch的值为value%rsi。由于程序执行时scanf报错,源头是%rsi的值不一样。然后继续回到gdb界面,在调用scanf之前设置断点(这里选择地址0x0000000000400552),观察%rsi的值是多少。首先设置断点,然后执行程序:(gdb)b*0x000000000040055f(gdb)run然后执行inforegisterrsi命令打印$rsi的值:rsi0x7ffffffffddf0140737488346608rsi0x4004504195408程序执行的结果面,地址0x7fffffffddf0是合法的0x400450是非法的。执行0x000000000040055f(scanf("%s",ch))时,两个版本分别将标准输入写入地址0x7ffffffffddf0和0x400450开始的连续内存空间。为什么地址0x400450是非法的?我们看一下Linuxx86-64运行时的内存映像:地址0x400450在Read-onlycodesegment区,所以写这个地址是非法的。在运行环境中,只能写栈和堆。为什么两个版本下rsi地址的值会有这么大的差异,一个在高地址内存空间,一个在低地址内存空间?在main函数执行之前,运行时已经对栈进行了入栈和出栈操作,地址-0x10(%rbp)和-0x18(%rbp)的值就是最后写入的数据入栈的时候,因为没有初始化,所以保留了上次操作的值(个人猜测)。