原创文章,转载请注明出处~
平台为兆芯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) !
综上,返回结果其实是正确的,只是一次乌龙事件而已,但是过程还是值得记录下来~