用GDB跟踪汇编代码排查返回值

11 篇文章 1 订阅

原创文章,转载请注明出处~

平台为兆芯x86-64 CPU

前边《嵌入式汇编实现系统调用-rename》中有发现用libc的rename函数实现的时候,如果原本的文件不存在,会返回-1(Operation not permitted),为了找到原因,我们用GDB跟踪汇编代码执行过程。

1.首先编译过程加入参数-g方便gdb调试:

root@henry-002:/usr/test_code# gcc rename_libc.c -o rename_libc_debug --static -g

2.使用gdb加载执行程序:

root@henry-002:/usr/test_code# gdb rename_libc_debug 

3.在rename处设置断点,这里需要知道,断点其实 是打在刚刚开始执行的位置,也就是触发断点的时候,这行代码并没有执行:

(gdb) list
1	#include <stdio.h>
2	#include <string.h>
3	int main(void)
4	{
5	    int error = -1;
6	    char *oldname = "hello.c";
7	    char *newname = "newhello.c";
8	    error = rename(oldname,newname);
9	    if(error == 0)
10	    {
(gdb) break 8
Breakpoint 1 at 0x40107d: file rename_libc.c, line 8.

4.要厘清的是为什么rename在没有找到文件的时候会返回-1,尝试使用step命令无法进入到rename内部,这样就必须使用汇编单步执行了,设置必要的选项:

(gdb) set disassemble-next-line on
(gdb) display $eax

这里设置了显示汇编代码的功能打开,并且每次停止都打印出eax的值,因为eax作为系统调用返回值的寄存器,我们要监控一下。

这里显示汇编也可以用display /i $rip  每次停止显示一条汇编指令,或者display /5i $rip每次显示5条汇编指令,还可以用layout asm指令将汇编显示在单独的窗口。

 

5.开始执行,可以看到中断触发并停止的时候显示汇编和eax的值:

(gdb) r
Starting program: /usr/test_code/rename_libc_debug 

Breakpoint 1, main () at rename_libc.c:8
8	    error = rename(oldname,newname);
=> 0x000000000040107d <main+31>:	48 8b 55 f8	mov    -0x8(%rbp),%rdx
   0x0000000000401081 <main+35>:	48 8b 45 f0	mov    -0x10(%rbp),%rax
   0x0000000000401085 <main+39>:	48 89 d6	mov    %rdx,%rsi
   0x0000000000401088 <main+42>:	48 89 c7	mov    %rax,%rdi
   0x000000000040108b <main+45>:	e8 70 6f 00 00	callq  0x408000 <rename>
   0x0000000000401090 <main+50>:	89 45 ec	mov    %eax,-0x14(%rbp)
1: $eax = 4198494

6.单步汇编执行:

(gdb) stepi
0x0000000000401081	8	    error = rename(oldname,newname);
   0x000000000040107d <main+31>:	48 8b 55 f8	mov    -0x8(%rbp),%rdx
=> 0x0000000000401081 <main+35>:	48 8b 45 f0	mov    -0x10(%rbp),%rax
   0x0000000000401085 <main+39>:	48 89 d6	mov    %rdx,%rsi
   0x0000000000401088 <main+42>:	48 89 c7	mov    %rax,%rdi
   0x000000000040108b <main+45>:	e8 70 6f 00 00	callq  0x408000 <rename>
   0x0000000000401090 <main+50>:	89 45 ec	mov    %eax,-0x14(%rbp)
1: $eax = 4198494
(gdb) stepi
0x0000000000401085	8	    error = rename(oldname,newname);
   0x000000000040107d <main+31>:	48 8b 55 f8	mov    -0x8(%rbp),%rdx
   0x0000000000401081 <main+35>:	48 8b 45 f0	mov    -0x10(%rbp),%rax
=> 0x0000000000401085 <main+39>:	48 89 d6	mov    %rdx,%rsi
   0x0000000000401088 <main+42>:	48 89 c7	mov    %rax,%rdi
   0x000000000040108b <main+45>:	e8 70 6f 00 00	callq  0x408000 <rename>
   0x0000000000401090 <main+50>:	89 45 ec	mov    %eax,-0x14(%rbp)
1: $eax = 4798980
(gdb) x /s $rax
0x493a04:	"hello.c"
(gdb) x /s $rdx
0x493a0c:	"newhello.c"
(gdb) stepi
0x0000000000401088	8	    error = rename(oldname,newname);
   0x000000000040107d <main+31>:	48 8b 55 f8	mov    -0x8(%rbp),%rdx
   0x0000000000401081 <main+35>:	48 8b 45 f0	mov    -0x10(%rbp),%rax
   0x0000000000401085 <main+39>:	48 89 d6	mov    %rdx,%rsi
=> 0x0000000000401088 <main+42>:	48 89 c7	mov    %rax,%rdi
   0x000000000040108b <main+45>:	e8 70 6f 00 00	callq  0x408000 <rename>
   0x0000000000401090 <main+50>:	89 45 ec	mov    %eax,-0x14(%rbp)
1: $eax = 4798980
(gdb) stepi
0x000000000040108b	8	    error = rename(oldname,newname);
   0x000000000040107d <main+31>:	48 8b 55 f8	mov    -0x8(%rbp),%rdx
   0x0000000000401081 <main+35>:	48 8b 45 f0	mov    -0x10(%rbp),%rax
   0x0000000000401085 <main+39>:	48 89 d6	mov    %rdx,%rsi
   0x0000000000401088 <main+42>:	48 89 c7	mov    %rax,%rdi
=> 0x000000000040108b <main+45>:	e8 70 6f 00 00	callq  0x408000 <rename>
   0x0000000000401090 <main+50>:	89 45 ec	mov    %eax,-0x14(%rbp)
1: $eax = 4798980
(gdb) stepi
0x0000000000408000 in rename ()
=> 0x0000000000408000 <rename+0>:	b8 52 00 00 00	mov    $0x52,%eax
1: $eax = 4798980
(gdb) stepi
0x0000000000408005 in rename ()
=> 0x0000000000408005 <rename+5>:	0f 05	syscall 
1: $eax = 82
(gdb) stepi
0x0000000000408007 in rename ()
=> 0x0000000000408007 <rename+7>:	48 3d 01 f0 ff ff	cmp    $0xfffffffffffff001,%rax
1: $eax = -2

这个过程,我们可以清楚地看到eax传递中断号82并执行syscall,执行完syscall后eax中的值是-2,这是符合我们预期的正常值。

7.继续汇编单步执行,因为前边的指令是cmp,下一条指令jae为条件跳转,这里jae是无符号数比较,%rax为-2用16进制无符号表示为0xfffffffffffffffe,要比$0xfffffffffffff001大,这里需要注意ATT格式的汇编和intel格式汇编操作数位置相反,所以这里返回-2则跳转,如果是返回0则比进行跳转:

(gdb) stepi
0x000000000040800d in rename ()
=> 0x000000000040800d <rename+13>:	0f 83 bd 09 03 00	jae    0x4389d0 <__syscall_error>
1: $eax = -2
(gdb) p $rax
$2 = -2
(gdb) p /a $rax
$3 = 0xfffffffffffffffe
(gdb) info all-registers 
rax            0xfffffffffffffffe	-2
rbx            0x4002b0	4194992
rcx            0x408007	4227079
rdx            0x493a0c	4798988
rsi            0x493a0c	4798988

............

8.继续单步执行:

(gdb) si
0x00000000004389d0 in __syscall_error ()
=> 0x00000000004389d0 <__syscall_error+0>:	48 f7 d8	neg    %rax
1: $eax = -2
(gdb) si
0x00000000004389d3 in __syscall_error ()
=> 0x00000000004389d3 <__syscall_error+3>:	64 89 04 25 c0 ff ff ff	mov    %eax,%fs:0xffffffffffffffc0
1: $eax = 2
(gdb) si
0x00000000004389db in __syscall_error ()
=> 0x00000000004389db <__syscall_error+11>:	48 83 c8 ff	or     $0xffffffffffffffff,%rax
1: $eax = 2
(gdb) si
0x00000000004389df in __syscall_error ()
=> 0x00000000004389df <__syscall_error+15>:	c3	retq   
1: $eax = -1

这段第一行先用neg指令进行取反,将-2变为2,然后将eax的值存到%fs:0xffffffffffffffc0的位置,再和$0xffffffffffffffff进行or操作,$0xffffffffffffffff就是-1,所以是这里将rax变为-1的

9.我们得到的返回值-1,并将返回值赋值给了error变量,error变量与0进行比较,跳入else分支执行打印error的动作:

(gdb) si
0x0000000000401090 in main () at rename_libc.c:8
8	    error = rename(oldname,newname);
   0x000000000040107d <main+31>:	48 8b 55 f8	mov    -0x8(%rbp),%rdx
   0x0000000000401081 <main+35>:	48 8b 45 f0	mov    -0x10(%rbp),%rax
   0x0000000000401085 <main+39>:	48 89 d6	mov    %rdx,%rsi
   0x0000000000401088 <main+42>:	48 89 c7	mov    %rax,%rdi
   0x000000000040108b <main+45>:	e8 70 6f 00 00	callq  0x408000 <rename>
=> 0x0000000000401090 <main+50>:	89 45 ec	mov    %eax,-0x14(%rbp)
1: $eax = -1
(gdb) si
9	    if(error == 0)
=> 0x0000000000401093 <main+53>:	83 7d ec 00	cmpl   $0x0,-0x14(%rbp)
   0x0000000000401097 <main+57>:	75 0c	jne    0x4010a5 <main+71>
1: $eax = -1
(gdb) si
0x0000000000401097	9	    if(error == 0)
   0x0000000000401093 <main+53>:	83 7d ec 00	cmpl   $0x0,-0x14(%rbp)
=> 0x0000000000401097 <main+57>:	75 0c	jne    0x4010a5 <main+71>
1: $eax = -1
(gdb) si
15	        printf("Rename fail! %s(%d) !\n",strerror(-error),error);
=> 0x00000000004010a5 <main+71>:	8b 45 ec	mov    -0x14(%rbp),%eax
   0x00000000004010a8 <main+74>:	f7 d8	neg    %eax
   0x00000000004010aa <main+76>:	89 c7	mov    %eax,%edi
   0x00000000004010ac <main+78>:	e8 df 83 01 00	callq  0x419490 <strerror>
   0x00000000004010b1 <main+83>:	8b 55 ec	mov    -0x14(%rbp),%edx
   0x00000000004010b4 <main+86>:	48 89 c6	mov    %rax,%rsi
   0x00000000004010b7 <main+89>:	bf 2c 3a 49 00	mov    $0x493a2c,%edi
   0x00000000004010bc <main+94>:	b8 00 00 00 00	mov    $0x0,%eax
   0x00000000004010c1 <main+99>:	e8 0a 6e 00 00	callq  0x407ed0 <printf>
1: $eax = -1

从上边过程我们可以看到syscall执行后返回值实际达到预期的-2,但是libc将-2存在某个内存位置,推测是errno变量地址,然后强制返回值为-1后返回,也就是说要么返回0,要么返回-1。

在glibc-2.19的源码中sysdeps\unix\x86_64\sysdep.S找到对应的代码:

/* Copyright (C) 2001-2014 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */

#include <sysdep.h>
#define _ERRNO_H
#include <bits/errno.h>
#include <tls.h>

#ifdef IS_IN_rtld
# include <dl-sysdep.h>		/* Defines RTLD_PRIVATE_ERRNO.  */
#endif

.globl C_SYMBOL_NAME(errno)
.globl syscall_error

__syscall_error:
#if defined (EWOULDBLOCK_sys) && EWOULDBLOCK_sys != EAGAIN
	/* We translate the system's EWOULDBLOCK error into EAGAIN.
	   The GNU C library always defines EWOULDBLOCK==EAGAIN.
	   EWOULDBLOCK_sys is the original number.  */
	cmp $EWOULDBLOCK_sys, %RAX_LP /* Is it the old EWOULDBLOCK?  */
	jne notb		/* Branch if not.  */
	movl $EAGAIN, %eax	/* Yes; translate it to EAGAIN.  */
notb:
#endif
#ifdef PIC
	movq C_SYMBOL_NAME(errno@GOTTPOFF)(%rip), %rcx
	movl %eax, %fs:0(%rcx)
#else
	movl %eax, %fs:C_SYMBOL_NAME(errno@TPOFF)
#endif
	or $-1, %RAX_LP
	ret

#undef	__syscall_error
END (__syscall_error)

10.我们还是看一下man的说明,原来libc的系统调用返回值就是0和-1,真正的错误码则放在errno中:

RENAME(2)                                                                              Linux Programmer's Manual                                                                              RENAME(2)

NAME
       rename - change the name or location of a file

SYNOPSIS
       #include <stdio.h>

       int rename(const char *oldpath, const char *newpath);

DESCRIPTION
       rename()  renames  a  file,  moving it between directories if required.  Any other hard links to the file (as created using link(2)) are unaffected.  Open file descriptors for oldpath are also
       unaffected.

       If newpath already exists it will be atomically replaced (subject to a few conditions; see ERRORS below), so that there is no point at which another process attempting to access  newpath  will
       find it missing.

       If oldpath and newpath are existing hard links referring to the same file, then rename() does nothing, and returns a success status.

       If newpath exists but the operation fails for some reason rename() guarantees to leave an instance of newpath in place.

       oldpath can specify a directory.  In this case, newpath must either not exist, or it must specify an empty directory.

       However, when overwriting there will probably be a window in which both oldpath and newpath refer to the file being renamed.

       If oldpath refers to a symbolic link the link is renamed; if newpath refers to a symbolic link the link will be overwritten.

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

11.重新修改代码:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(void)
{
    int error = -1;
    char *oldname = "hello.c";
    char *newname = "newhello.c";
    error = rename(oldname,newname);
    if(error == 0)
    {
        printf("Rename successfully!\n");
    }
    else
    {
        printf("Rename fail! %s(%d) !\n",strerror(errno),errno);
    }
}

12.编译并执行,结果正常:

root@henry-002:/usr/test_code# gcc rename_libc_modify.c -o rename_libc_modify -g --static
root@henry-002:/usr/test_code# 
root@henry-002:/usr/test_code# 
root@henry-002:/usr/test_code# ./rename_libc_modify
Rename fail! No such file or directory(2) !

综上,返回结果其实是正确的,只是一次乌龙事件而已,但是过程还是值得记录下来~

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值