Linux x86平台获取sys_call_table(1)

为了做好运维面试路上的助攻手,特整理了上百道 【运维技术栈面试题集锦】 ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。

本份面试集锦涵盖了

  • 174 道运维工程师面试题
  • 128道k8s面试题
  • 108道shell脚本面试题
  • 200道Linux面试题
  • 51道docker面试题
  • 35道Jenkis面试题
  • 78道MongoDB面试题
  • 17道ansible面试题
  • 60道dubbo面试题
  • 53道kafka面试
  • 18道mysql面试题
  • 40道nginx面试题
  • 77道redis面试题
  • 28道zookeeper

总计 1000+ 道面试题, 内容 又全含金量又高

  • 174道运维工程师面试题

1、什么是运维?

2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

3、现在给你三百台服务器,你怎么对他们进行管理?

4、简述raid0 raid1raid5二种工作模式的工作原理及特点

5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

7、Tomcat和Resin有什么区别,工作中你怎么选择?

8、什么是中间件?什么是jdk?

9、讲述一下Tomcat8005、8009、8080三个端口的含义?

10、什么叫CDN?

11、什么叫网站灰度发布?

12、简述DNS进行域名解析的过程?

13、RabbitMQ是什么东西?

14、讲一下Keepalived的工作原理?

15、讲述一下LVS三种模式的工作过程?

16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

17、如何重置mysql root密码?

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

rax:系统调用号  参数传递:rdi,rsi,rdx,r10、r8、r9  (rcx -> r10)

(2)
在x86_64架构中,调用sys_call_table的机器码操作是通过间接调用(indirect call)指令来实现的。具体的操作码是ff 14 c5,其表示的汇编指令是callq *%rax。

这条指令的作用是从rax寄存器中获取一个指针地址,然后跳转到该地址执行代码。在这种情况下,我们假设rax寄存器中存储了sys_call_table的地址,以便通过间接调用来调用相应的系统调用函数。

我们通过crash调试便可以获取到sys_call_table的地址:
在这里插入图片描述
由上面的汇编代码:

system_call
	-->system_call_fastpath
		-->call \*sys\_call\_table(,%rax,8)

我们反汇编system_call_fastpath:

crash> dis system_call_fastpath
0xffffffff816b4fb3 <system_call_fastpath>:      cmp    $0x146,%rax
0xffffffff816b4fb9 <system_call_fastpath+6>:    ja     0xffffffff816b5081 <badsys>
0xffffffff816b4fbf <system_call_fastpath+12>:   mov    %r10,%rcx
0xffffffff816b4fc2 <system_call_fastpath+15>:   callq  \*-0x7e941120(,%rax,8)
0xffffffff816b4fc9 <system_call_fastpath+22>:   mov    %rax,0x20(%rsp)

可以看到在地址0xffffffff816b4fc2,调用call *sys_call_table(,%rax,8),然后读取内存地址0xffffffff816b4fc2的值:

crash> rd -64 0xffffffff816b4fc2
ffffffff816b4fc2:  48816beee0c514ff                    .....k.H

call的操作码是0xff14c5,后面就是sys_call_table的地址0x816beee0
因此sys_call_table的地址是0xffffffff816beee0。

备注:第三节使用了rdmsr来获取 system_call 符号的值。

(3)
我们也可以借助vmlinux来objdump来获取其地址:

# ./extract-vmlinux vmlinuz-3.10.0-693.el7.x86_64 > vmlinux
# objdump -d vmlinux > vmlinux.txt
# vim vmlinux.txt

我们根据 ff 14 c5 指令码来搜索,上一条指令且是movq %r10,%rcx:

	movq %r10,%rcx
	call \*sys\_call\_table(,%rax,8)  # XXX:	 rip relative

ffffffff816b4fbf:       4c 89 d1                mov    %r10,%rcx
ffffffff816b4fc2:       ff 14 c5 e0 ee 6b 81    callq  \*-0x7e941120(,%rax,8)

可以看到 ff 14 c5 机器码后面的地址就是sys_call_table的地址0x816beee0(x86_64是小端机器)。
因此sys_call_table的地址是0xffffffff816beee0。

二、使用dump_stack

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>

static int __init lkm\_init(void)
{
	dump\_stack();
	return 0;
}


static void __exit lkm\_exit(void)
{
	
}

module\_init(lkm_init);
module\_exit(lkm_exit);

MODULE\_LICENSE("GPL");

[ 7666.386756] Call Trace:
[ 7666.386761]  [<ffffffff816a3d91>] dump_stack+0x19/0x1b
[ 7666.386762]  [<ffffffffc01f3009>] lkm_init+0x9/0x1000 [sys_call_table]
[ 7666.386764]  [<ffffffff810020e8>] do_one_initcall+0xb8/0x230
[ 7666.386766]  [<ffffffff81100734>] load_module+0x1f64/0x29e0
[ 7666.386769]  [<ffffffff8134bbf0>] ? ddebug_proc_write+0xf0/0xf0
[ 7666.386770]  [<ffffffff810fcdd3>] ? copy_module_from_fd.isra.42+0x53/0x150
[ 7666.386772]  [<ffffffff81101366>] SyS_finit_module+0xa6/0xd0
[ 7666.386774]  [<ffffffff816b4fc9>] system_call_fastpath+0x16/0x1b

我们就可以看到 system_call_fastpath+0x16 的地址是0xffffffff816b4fc9,因此system_call_fastpath的地址是:

0xffffffff816b4fc9 - 0x16 = 0xffffffff816b4fb3

我们可以从/proc/kallsyms验证:

# cat /proc/kallsyms | grep system\_call\_fastpath
ffffffff816b4fb3 t system_call_fastpath

system_call_fastpath:
#if \_\_SYSCALL\_MASK == ~0
	cmpq $__NR_syscall_max,%rax
#else
	andl $__SYSCALL_MASK,%eax
	cmpl $__NR_syscall_max,%eax
#endif
	ja badsys
	movq %r10,%rcx
	call \*sys\_call\_table(,%rax,8)  # XXX:	 rip relative

这里我们简单点,从上面的crash可以看到:

crash> dis system_call_fastpath
0xffffffff816b4fb3 <system_call_fastpath>:      cmp    $0x146,%rax
0xffffffff816b4fb9 <system_call_fastpath+6>:    ja     0xffffffff816b5081 <badsys>
0xffffffff816b4fbf <system_call_fastpath+12>:   mov    %r10,%rcx
0xffffffff816b4fc2 <system_call_fastpath+15>:   callq  \*-0x7e941120(,%rax,8)

call *sys_call_table 在 system_call_fastpath 的 0xf(15)处。

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>

static int __init lkm\_init(void)
{
	int i;
	unsigned char \*ptr;

	ptr = (unsigned char \*)(0xffffffff816b4fc9 - 0x16 + 0xf);

	for (i = 0; i < 8; i ++) {
			//printk("%02x ", (unsigned char)ptr[i]);
			printk("%02x ", (unsigned char)\*(ptr + i));
	}

	return 0;
}


static void __exit lkm\_exit(void)
{

}

module\_init(lkm_init);
module\_exit(lkm_exit);

MODULE\_LICENSE("GPL");

(unsigned char)ptr[i]) = (unsigned char)\*(ptr + i))

# insmod sys\_call\_table.ko
[# dmesg -c
[ 8416.858466] ff 14 c5 e0 ee 6b 81 48

可以看到 ff 14 c5 机器码后面的地址就是sys_call_table的地址0x816beee0(x86_64是小端机器)。
因此sys_call_table的地址是0xffffffff816beee0。

或者直接搜索:

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>

static int __init lkm\_init(void)
{
	int i, j;
	unsigned char \*ptr;

	ptr = (unsigned char \*)(0xffffffff816b4fc9 - 0x16 + 0xf);

 	for (i = 0; i < 20; i ++) {

		if( ((unsigned char)ptr[i] == 0xff) && ((unsigned char)ptr[i + 1] == 0x14) && ((unsigned char)ptr[i +2] == 0xc5) ){
			printk("0x%x ", \*(unsigned int \*)(ptr+i+3));
			break;
		}
	} 


	return 0;
}


static void __exit lkm\_exit(void)
{

}

module\_init(lkm_init);
module\_exit(lkm_exit);

MODULE\_LICENSE("GPL");

# insmod sys\_call\_table.ko
# dmesg -c
[  280.502659] 0x816beee0

因此sys_call_table的地址是0xffffffff816beee0。

三、根据MSR_LSTAR寄存器

我们来看一下内核的启动过程:

start\_kernel()
	-->trap\_init()
		-->cpu\_init()
			-->syscall\_init()

#define MSR\_STAR 0xc0000081 /\* legacy mode SYSCALL target \*/
#define MSR\_LSTAR 0xc0000082 /\* long mode SYSCALL target \*/
#define MSR\_CSTAR 0xc0000083 /\* compat mode SYSCALL target \*/

| Register

AddressArchitectural MSR Name / Bit Fields (Former MSR Name)MSR/Bit Description
C000_0081HIA32_STARSystem Call Target Address (R/W)
C000_0082HIA32_LSTARIA-32e Mode System Call Target Address (R/W) Target RIP for the called procedure when SYSCALL is executed in 64-bit mode.
C000_0083HIA32_CSTARIA-32e Mode System Call Target Address (R/W) Not used, as the SYSCALL instruction is not recognized in compatibility mode.

我们主要看MSR_LSTAR寄存器:
IA32_LSTAR 是 IA-32e 模式下的系统调用目标地址寄存器,用于存储在 64 位模式下执行 SYSCALL 指令时被调用过程的目标 RIP(指令指针)。

当在 64 位模式下执行 SYSCALL 指令时,系统将从 IA32_LSTAR 寄存器中加载 RIP 的值,以跳转至相应的系统调用处理程序。

为了使内核接收传入的系统调用,它必须通过将其地址写入IA32_LSTAR MSR寄存器 来注册将在发生系统调用时执行的代码的地址。

/\* May not be marked \_\_init: used by software suspend \*/
void syscall\_init(void)
{
	/\*
 \* LSTAR and STAR live in a bit strange symbiosis.
 \* They both write to the same internal register. STAR allows to
 \* set CS/DS but only a 32bit target. LSTAR sets the 64bit rip.
 \*/
	wrmsrl(MSR_STAR,  ((u64)__USER32_CS)<<48  | ((u64)__KERNEL_CS)<<32);
	wrmsrl(MSR_LSTAR, system_call);
	wrmsrl(MSR_CSTAR, ignore_sysret);
	......
	/\* Flags to clear on syscall \*/
	wrmsrl(MSR_SYSCALL_MASK,
	       X86_EFLAGS_TF|X86_EFLAGS_DF|X86_EFLAGS_IF|
	       X86_EFLAGS_IOPL|X86_EFLAGS_AC);
}

这行代码把system_call入口地址存入到MSR_LSTAR寄存器。syscall指令会把该地址加载到到%rip寄存器,从该地址开始执行。

syscall指令:
在这里插入图片描述
SYSCALL 指令用于在特权级别 0(内核模式)下调用操作系统的系统调用处理程序。它通过从 IA32_LSTAR MSR 寄存器加载 RIP(同时将 SYSCALL 指令后面的指令地址保存在 RCX 中)来实现这一功能。IA32_LSTAR MSR 寄存器的值是一个规范地址(canonical address),通过 WRMSR 指令确保其始终包含一个规范地址。

SYSCALL 指令:RIP = IA32_LSTAR MSR 寄存器 = system_call

过程如下:
在这里插入图片描述

特别说明一下,因为初始化时,掩码中包含中断标志位X86_EFLAGS_IF,所以syscall指令执行时,中断是禁止的。

我们可以看到在syscall_init中将 system_call 的地址写入了 MSR_LSTAR 寄存器:

wrmsrl(MSR_LSTAR, system_call);

那么我们读取MSR_LSTAR寄存器就可以获取到system_call的地址,进入获取到sys_call_table的地址。

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>


static int __init lkm\_init(void)
{
    unsigned long msr_lstar;

	rdmsrl(MSR_LSTAR, msr_lstar);

	printk("MSR\_LSTAR = %lx\n", msr_lstar);

	return 0;
}


static void __exit lkm\_exit(void)
{

}

module\_init(lkm_init);
module\_exit(lkm_exit);

MODULE\_LICENSE("GPL");

]# cat /proc/kallsyms | grep '\<system\_call\>'
ffffffff816b4f50 T system_call
# insmod get\_msr.ko
# dmesg -c
[ 1474.209016] MSR_LSTAR = ffffffff816b4f50

获取到了system_call的地址那么解析其后面的字节便可以找到sys_call_table的地址。

完整代码:

#include <linux/module.h>
#include <linux/kernel.h>

void \*memmem(const void \*haystack, size\_t haystack_size, const void \*needle, size\_t needle_size )
{
    char \*p;

    for (p = (char \*)haystack; p <= ((char \*)haystack - needle_size + haystack_size); p++ )
        if (memcmp(p, needle, needle_size) == 0 )
            return (void \*)p;

    return NULL;
}

unsigned long \*find\_sys\_call\_table ( void )
{
    char \*\*p;
    unsigned long sct_off = 0;
    unsigned char code[512];

    rdmsrl(MSR_LSTAR, sct_off);
    memcpy(code, (void \*)sct_off, sizeof(code));

    p = (char \*\*)memmem(code, sizeof(code), "\xff\x14\xc5", 3);

    if (p)
    {
        unsigned long \*sct = \*(unsigned long \*\*)((char \*)p + 3);

        // Stupid compiler doesn't want to do bitwise math on pointers
        sct = (unsigned long \*)(((unsigned long)sct & 0xffffffff) | 0xffffffff00000000);

        return sct;
    }
    else
        return NULL;
}


static int __init lkm\_init(void)
{
    unsigned long \*sys_call_table = find\_sys\_call\_table();
    printk("The sys\_call\_table address is:%lx\n",(unsigned long)sys_call_table);
    return 0;
}


static void __exit lkm\_exit(void)
{

}
module\_init(init_get_sys_call_table);
module\_exit(exit_get_sys_call_table);

MODULE\_LICENSE("GPL");

# cat /proc/kallsyms | grep '\<sys\_call\_table\>'
ffffffff816beee0 R sys_call_table
# insmod sys\_call\_table.ko
# dmesg -c
[ 2363.360239] The sys_call_table address is:0xffffffff816beee0

四、使用sys_close

// linux-3.10/fs/open.c

/\*
 \* Careful here! We test whether the file pointer is NULL before
 \* releasing the fd. This ensures that one clone task can't release
 \* an fd while another clone is opening it.
 \*/
SYSCALL\_DEFINE1(close, unsigned int, fd)
{
	int retval = \_\_close\_fd(current->files, fd);

	/\* can't restart close syscall because file table entry was cleared \*/
	if (unlikely(retval == -ERESTARTSYS ||
		     retval == -ERESTARTNOINTR ||
		     retval == -ERESTARTNOHAND ||
		     retval == -ERESTART_RESTARTBLOCK))
		retval = -EINTR;

	return retval;
}
EXPORT\_SYMBOL(sys_close);

EXPORT\_SYMBOL(sys_close);

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

t->files, fd);

/\* can't restart close syscall because file table entry was cleared \*/
if (unlikely(retval == -ERESTARTSYS ||
	     retval == -ERESTARTNOINTR ||
	     retval == -ERESTARTNOHAND ||
	     retval == -ERESTART_RESTARTBLOCK))
	retval = -EINTR;

return retval;

}
EXPORT_SYMBOL(sys_close);



EXPORT_SYMBOL(sys_close);



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618635766)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值