Cómo reemplazar una aplicación Linux función del núcleo - parche caliente Principio

Ayer por la noche juró. A continuación, la nave no escribirá gancho binario, y hoy hay usuarios consultar los detalles técnicos, y, finalmente, no pudieron resistir ...

Con el fin de no violar el juramento incluso casualmente decir, hoy no escriba gancho binario, hoy escrito en lenguaje C, binario simplemente mojar los bordes!

Mira el tema, implementaciones alternativas de la función del núcleo de Linux , ¿qué? No es eso lo kpatch! Eso es lo que llamamos el parche caliente . Hacemos parches calientes para el kernel cuando nadie asamblea para escribir, nadie deletrear la escritura lógica binaria que, por lo general, están directamente modificar el código C de la función del núcleo, a continuación, forman un archivo de revisión, y luego ... y luego ... documentos leídos kpatch de ella.

En este artículo voy a describir el principio de parche térmico, en lugar de cómo utilizar kpatch de Howto, más que en cualquier fuente kpatch análisis técnico.

Solución de error parche caliente a un ejemplo práctico del kernel 3.10 comienza nuestra historia.

En este ejemplo, hemos modificado la realización de set_next_buddy:

diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
...
@@ -4537,8 +4540,11 @@ static void set_next_buddy(struct sched_entity *se)
    if (entity_is_task(se) && unlikely(task_of(se)->policy == SCHED_IDLE))
        return;

-   for_each_sched_entity(se)
+   for_each_sched_entity(se) {
+       if (!se->on_rq)
+           return;
        cfs_rq_of(se)->next = se;
+   }
 }

Parece, con el fin de corregir un error conocido, tenemos que añadir unas pocas líneas de código de función set_next_buddy, está claro que esto es fácil.

Esto añade unas pocas líneas de código más tarde, formaron una nueva función set_next_buddy, con el fin de permitir nuevas funciones correr arriba, y ahora se enfrentan a tres problemas:

  • ¿Cómo podemos usar esta nueva función set_next_buddy compilado en binario?
  • ¿Cómo funcionamos este nuevo código binario set_next_buddy inyectada en el núcleo en ejecución?
  • ¿Cómo reemplazar la antigua con la nueva función binaria set_next_buddy set_next_buddy?

Vemos un problema.

En primer lugar, la primera pregunta es muy fácil de resolver.

Hemos modificado un fichero C kernel / sched / fair.c, con el fin de resolver las dependencias en tiempo de compilación, archivo de revisión que desea modificar justo después de la formación en el árbol de fuentes de núcleo en ejecución puede ser compilado, y similares por objdump mecanismo, que puede retirarse de forma binaria compilada set_next_buddy un archivo obj, y luego hacer un ko no es una cosa difícil. Esto forma un módulo de núcleo, similar kpatch-y8u59dkv.ko

A continuación, vistazo a la segunda pregunta, archivo ko cómo formar la primera pregunta en binario set_next_buddy inyectada en el núcleo de la misma?

No es difícil, kpatch mecanismo módulo de carga que está haciendo. parche caliente en el núcleo habrá dos funciones set_next_buddy:

crash> dis set_next_buddy
dis: set_next_buddy: duplicate text symbols found:
// 老的set_next_buddy
ffffffff810b9450 (t) set_next_buddy /usr/src/debug/kernel-3.10.0/linux-3.10.0.x86_64/kernel/sched/fair.c: 4536
// 新的set_next_buddy
ffffffffa0382410 (t) set_next_buddy [kpatch_y8u59dkv]

Mediante la tercera cuestión, un pequeño problema. ¿Cómo funciona el nuevo binario set_next_buddy reemplazar el antiguo binaria set_next_buddy ella?

Obviamente, la cubierta no se puede utilizar, debido a que el diseño de la función del núcleo es muy compacto y continuo, cuando cada función en el espacio del núcleo formado en el conjunto, si la nueva función mucho más grande que la función de edad, se sobrescribirá otra transfronteriza función.

Usando mi artículo anterior describió la tecnología gancho binario es factible, tales como los métodos de artículo siguientes:
https://blog.csdn.net/dog250/article/details/105206753
binaria diff, a continuación, empuje compacto necesidad de modificar el lugar esto es, sin duda, un golpe! Sin embargo, este método no es elegante, lleno de inteligente, pero inútil, su mayor problema es el gerente inversa.

El método más normal es usar ftrace gancho, es decir, función modificada en el comienzo de la vieja stub byte ftrace 5, que será modificado para instruir a la "JMP / llamar a la nueva función", y la función del marco de pila de edad de salto en las funciones de código auxiliar. De esta manera por completo la función de derivación de edad.

Nos fijamos en dos set_next_buddy del binario antes mencionadas:

// 老的set_next_buddy:
crash> dis ffffffff810b9450 4
// 注意,老函数的ftrace stub已经被替换
0xffffffff810b9450 <set_next_buddy>:    callq  0xffffffff81646df0 <ftrace_regs_caller>
// 后面这些如何被绕过呢?ftrace_regs_caller返回后如何被skip掉呢?这需要平衡堆栈的技巧!
// 后面通过实例来讲如何平衡堆栈,绕过老的函数。
0xffffffff810b9455 <set_next_buddy+5>:  push   %rbp
0xffffffff810b9456 <set_next_buddy+6>:  cmpq   $0x0,0x150(%rdi)
0xffffffff810b945e <set_next_buddy+14>: mov    %rsp,%rbp
// 新的set_next_buddy:
crash> dis ffffffffa0382410 4
// 新函数则是ftrace_regs_caller最终要调用的函数
0xffffffffa0382410 <set_next_buddy>:    nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa0382415 <set_next_buddy+5>:  push   %rbp
0xffffffffa0382416 <set_next_buddy+6>:  cmpq   $0x0,0x150(%rdi)
0xffffffffa038241e <set_next_buddy+14>: mov    %rsp,%rbp

Este es el principio de un parche de calor.

En este artículo se habla aquí de papel, este sería el final de la vergüenza y el arrepentimiento, entonces yo uso un ejemplo práctico para ilustrar esto. Este ejemplo es muy simple, sólo unos arreglos y será capaz de subir y ver el efecto.

No me gusta comparar análisis de código fuente, por lo que no vamos a entrar desplazamientos código fuente ftrace_regs_caller comentario, utilizo mi propia forma de lograr necesidades similares, y mucho más simple, lo cual es muy beneficioso para nuestros trabajadores a comprender la naturaleza de las cosas.

Mi ejemplo no irá a parchear el kernel función tanto de mis ejemplos parche es un módulo del núcleo sencillo que escribí en la función, el código del módulo es la siguiente:

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

// 下面的sample_read就是我将要patch的函数
static ssize_t sample_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int n = 0;
	char kb[16];

	if (*ppos != 0) {
		return 0;
	}

	n = sprintf(kb, "%d\n", 1234);
	memcpy(ubuf, kb, n);
	*ppos += n;
	return n;
}

static struct file_operations sample_ops = {
	.owner = THIS_MODULE,
	.read = sample_read,
};

static struct proc_dir_entry *ent;
static int __init sample_init(void)
{
	ent = proc_create("test", 0660, NULL, &sample_ops);
	if (!ent)
		return -1;

	return 0;
}

static void __exit sample_exit(void)
{
	proc_remove(ent);
}

module_init(sample_init);
module_exit(sample_exit);
MODULE_LICENSE("GPL");

Lo cargamos, y luego ir a leer sobre el directorio / proc / test:

[root@localhost test]# insmod sample.ko
[root@localhost test]# cat /proc/test
1234

OK, todo para hacerlo. En este punto, nos fijamos en la parte delantera de sample_read 5 bytes:

crash> dis sample_read 1
0xffffffffa038c000 <sample_read>:       nopl   0x0(%rax,%rax,1) [FTRACE NOP]

Ven en premisa sample.ko ya cargado, nos parche. Mi objetivo es, Fix a cabo la función sample_read para que devuelva 4321 en lugar de 1234.

El siguiente es el código de puntos completos están en un comentario:

// hijack.c
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/cpu.h>

char *stub;
char *addr = NULL;

// 可以用JMP模式,也可以用CALL模式
//#define JMP	1

// 和sample模块里同名的sample_read函数
static ssize_t sample_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int n = 0;
	char kb[16];

	if (*ppos != 0) {
		return 0;
	}
	// 这里我们把1234的输出给fix成4321的输出
	n = sprintf(kb, "%d\n", 4321);
	memcpy(ubuf, kb, n);
	*ppos += n;
	return n;
}

// hijack_stub的作用就类似于ftrace kpatch里的ftrace_regs_caller
static ssize_t hijack_stub(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	// 用nop占位,加上C编译器自动生成的函数header代码,这么大的函数来容纳stub应该够了。
	asm ("nop; nop; nop; nop; nop; nop; nop; nop;");
	return 0;
}

#define FTRACE_SIZE   	5
#define POKE_OFFSET		0
#define POKE_LENGTH		5
#define SKIP_LENGTH		8

static unsigned long *(*_mod_find_symname)(struct module *mod, const char *name);
static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
static struct mutex *_text_mutex;
unsigned char saved_inst[POKE_LENGTH];
struct module *mod;

static int __init hotfix_init(void)
{
	unsigned char jmp_call[POKE_LENGTH];
	unsigned char e8_skip_stack[SKIP_LENGTH];
	s32 offset, i = 5;

	mod = find_module("sample");
	if (!mod) {
		printk("没加载sample模块,你要patch个啥?\n");
		return -1;
	}
	_mod_find_symname = (void *)kallsyms_lookup_name("mod_find_symname");
	if (!_mod_find_symname) {
		printk("还没开始,就已经结束。");
		return -1;
	}
	addr = (void *)_mod_find_symname(mod, "sample_read");
	if (!addr) {
		printk("一切还没有准备好!请先加载sample模块。\n");
		return -1;
	}
	_text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
	_text_mutex = (void *)kallsyms_lookup_name("text_mutex");
	if (!_text_poke_smp || !_text_mutex) {
		printk("还没开始,就已经结束。");
		return -1;
	}

	stub = (void *)hijack_stub;

	offset = (s32)((long)sample_read - (long)stub - FTRACE_SIZE);

	// 下面的代码就是stub函数的最终填充,它类似于ftrace_regs_caller的作用!
	e8_skip_stack[0] = 0xe8;
	(*(s32 *)(&e8_skip_stack[1])) = offset;
#ifndef JMP	// 如果是call模式,则需要手工平衡堆栈,跳过原始函数的栈帧
	e8_skip_stack[i++] = 0x41; // pop %r11
	e8_skip_stack[i++] = 0x5b; // r11寄存器为临时使用寄存器,遵循调用者自行保护原则
#endif
	e8_skip_stack[i++] = 0xc3;
	_text_poke_smp(&stub[0], e8_skip_stack, SKIP_LENGTH);

	offset = (s32)((long)stub - (long)addr - FTRACE_SIZE);

	memcpy(&saved_inst[0], addr, POKE_LENGTH);
#ifndef JMP
	jmp_call[0] = 0xe8;
#else
	jmp_call[0] = 0xe9;
#endif
	(*(s32 *)(&jmp_call[1])) = offset;
	get_online_cpus();
	mutex_lock(_text_mutex);
	_text_poke_smp(&addr[POKE_OFFSET], jmp_call, POKE_LENGTH);
	mutex_unlock(_text_mutex);
	put_online_cpus();

	return 0;
}

static void __exit hotfix_exit(void)
{
	mod = find_module("sample");
	if (!mod) {
		printk("一切已经结束!\n");
		return;
	}
	addr = (void *)_mod_find_symname(mod, "sample_read");
	if (!addr) {
		printk("一切已经结束!\n");
		return;
	}
	get_online_cpus();
	mutex_lock(_text_mutex);
	_text_poke_smp(&addr[POKE_OFFSET], &saved_inst[0], POKE_LENGTH);
	mutex_unlock(_text_mutex);
	put_online_cpus();
}

module_init(hotfix_init);
module_exit(hotfix_exit);
MODULE_LICENSE("GPL");

Bien, ahora lo cargamos y, a continuación, vuelva a leer sobre el directorio / proc / test:

[root@localhost test]# insmod ./hijack.ko
[root@localhost test]# cat /proc/test
4321

Se puede ver ya el parche correctamente. Al final lo que pasó? Buscamos desmontar:

crash> dis sample_read
dis: sample_read: duplicate text symbols found:
ffffffffa039d000 (t) sample_read [sample]
ffffffffa03a2020 (t) sample_read [hijack]
crash>

Ah, se han realizado dos símbolos de función sample_read del mismo nombre, el módulo de la muestra donde la función es viejo, y no es una función de módulo de secuestrar el nuevo arreglo. Nos fijamos en cada uno:

// 先看老的sample_read,它的ftrace stub已经被改成了call hijack_stub
crash> dis ffffffffa039d000 1
0xffffffffa039d000 <sample_read>:       callq  0xffffffffa03a2000 <hijack_stub>
// 再看新的sample_read,它就是最终被执行的函数
crash> dis ffffffffa03a2020 1
0xffffffffa03a2020 <sample_read>:       nopl   0x0(%rax,%rax,1) [FTRACE NOP]
crash>

Cuando el nuevo sample_read terminó, después de regresar hijack_stub, si el modo de llamadas donde la necesidad de saltar fuera de la función sample_read marco de pila de edad, por lo que un estallido% r11 para completarla, directamente después de lata ret, si el modo de JMP, ret directa, no es necesario saltar marco de pila, porque la instrucción JMP no empujaría.

Bueno, esto es lo que yo quiero decir la historia. Para decirlo claramente, que se describe aquí una nave aún con vida, sólo quiero utilizar de la manera más simple que todo el mundo puede entender, para mostrar para lograr parche térmica relativamente complejo de principios. Creo que los trabajadores necesitan tener un conocimiento profundo de los principios subyacentes.

Los gerentes también les gusta comer chile, pero no del todo, pero es obvio que el gerente no puede espolvorear agua.


Wenzhou zapatos mojados, el agua de lluvia no grasa!

发布了1583 篇原创文章 · 获赞 5118 · 访问量 1114万+

Supongo que te gusta

Origin blog.csdn.net/dog250/article/details/105254739
Recomendado
Clasificación