From 751673dac23b7345c3e15e4aa81043019137463d Mon Sep 17 00:00:00 2001 From: renoseven Date: Mon, 30 Jun 2025 13:41:04 +0800 Subject: [PATCH] upatch-manage: resolve dead lock issue Signed-off-by: renoseven --- upatch-manage/main.c | 8 +- upatch-manage/patch_entity.c | 231 ++++------ upatch-manage/patch_entity.h | 39 +- upatch-manage/patch_load.c | 57 +-- upatch-manage/patch_manage.c | 802 ++++++++++++++++++++------------- upatch-manage/patch_manage.h | 6 +- upatch-manage/process_entity.c | 154 ++++--- upatch-manage/process_entity.h | 20 +- upatch-manage/symbol_resolve.c | 2 +- upatch-manage/target_entity.c | 379 +++++++++------- upatch-manage/target_entity.h | 108 ++--- upatch-manage/util.h | 9 +- 12 files changed, 1007 insertions(+), 808 deletions(-) diff --git a/upatch-manage/main.c b/upatch-manage/main.c index 7263a756..060582b3 100644 --- a/upatch-manage/main.c +++ b/upatch-manage/main.c @@ -22,10 +22,9 @@ #include #include -#include "kernel_compat.h" #include "ioctl_dev.h" -#include "patch_entity.h" -#include "target_entity.h" +#include "patch_manage.h" +#include "kernel_compat.h" #include "util.h" #ifndef MODNAME @@ -65,8 +64,7 @@ static int __init upatch_module_init(void) */ static void __exit upatch_module_exit(void) { - report_patch_table_populated(); - report_target_table_populated(); + report_global_table_populated(); kernel_compat_exit(); ioctl_device_exit(); diff --git a/upatch-manage/patch_entity.c b/upatch-manage/patch_entity.c index 4d5832bf..891418a5 100644 --- a/upatch-manage/patch_entity.c +++ b/upatch-manage/patch_entity.c @@ -26,8 +26,6 @@ #include "patch_load.h" #include "util.h" -#define PATCH_TABLE_HASH_BITS 4 - static const char *SYMTAB_NAME = ".symtab"; static const char *TEXT_RELA_NAME = ".rela.text."; @@ -35,9 +33,6 @@ static const char *UPATCH_FUNCS_NAME = ".upatch.funcs"; static const char *UPATCH_FUNCS_RELA_NAME = ".rela.upatch.funcs"; static const char *UPATCH_STRINGS_NAME = ".upatch.strings"; -DEFINE_HASHTABLE(g_patch_table, PATCH_TABLE_HASH_BITS); -DEFINE_MUTEX(g_patch_table_lock); - static inline void count_und_symbol(struct patch_metadata *meta, Elf_Sym *symtab, size_t count) { size_t i; @@ -60,14 +55,23 @@ static inline void count_got_reloc(struct patch_metadata *meta, Elf_Rela *relas, } } -static void clear_patch_metadata(struct patch_metadata *meta) +static void destroy_patch_metadata(struct patch_metadata *meta) { - VFREE_CLEAR(meta->file_buff); - meta->file_size = 0; + KFREE_CLEAR(meta->path); + iput(meta->inode); + meta->inode = NULL; + + VFREE_CLEAR(meta->buff); + meta->size = 0; + meta->shstrtab_index = 0; meta->symtab_index = 0; meta->strtab_index = 0; + meta->func_index = 0; + meta->rela_index = 0; + meta->string_index = 0; + meta->funcs = NULL; meta->strings = NULL; @@ -78,11 +82,9 @@ static void clear_patch_metadata(struct patch_metadata *meta) meta->got_reloc_num = 0; } -static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patch) +static int resolve_patch_metadata(struct patch_metadata *meta, const char *file_path) { - int ret = 0; - - loff_t file_size; + struct file *file; Elf_Ehdr *ehdr; Elf_Shdr *shdrs; @@ -99,26 +101,49 @@ static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patc struct upatch_relocation *relas = NULL; size_t rela_num = 0; - meta->file_size = i_size_read(file_inode(patch)); - meta->file_buff = vmalloc_read(patch, 0, meta->file_size); - if (IS_ERR(meta->file_buff)) { - ret = PTR_ERR(meta->file_buff); - log_err("failed to read file, len=0x%llx\n", meta->file_size); - goto fail; + int ret = 0; + + file = filp_open(file_path, O_RDONLY, 0); + if (IS_ERR(file)) { + log_err("failed to open '%s'\n", file_path); + ret = PTR_ERR(file); + file = NULL; + goto out; + } + + meta->path = kstrdup(file_path, GFP_KERNEL); + if (!meta->path) { + log_err("faild to alloc file path\n"); + ret = -ENOMEM; + goto out; } - ehdr = meta->file_buff; - if (!is_valid_patch(ehdr, meta->file_size)) { - ret = -ENOEXEC; + meta->inode = igrab(file_inode(file)); + if (!meta->inode) { + log_err("file '%s' inode is invalid\n", meta->path); + ret = -ENOENT; + goto out; + } + + meta->size = i_size_read(meta->inode); + meta->buff = vmalloc_read(file, 0, meta->size); + if (IS_ERR(meta->buff)) { + log_err("failed to read file, len=0x%llx\n", meta->size); + ret = PTR_ERR(meta->buff); + goto out; + } + + ehdr = meta->buff; + if (!is_valid_patch(ehdr, meta->size)) { log_err("invalid file format\n"); - goto fail; + ret = -ENOEXEC; + goto out; } meta->shstrtab_index = ehdr->e_shstrndx; - file_size = meta->file_size; - shdrs = meta->file_buff + ehdr->e_shoff; + shdrs = meta->buff + ehdr->e_shoff; shdr_num = ehdr->e_shnum; - shstrtab = meta->file_buff + shdrs[meta->shstrtab_index].sh_offset; + shstrtab = meta->buff + shdrs[meta->shstrtab_index].sh_offset; shstrtab_size = shdrs[meta->shstrtab_index].sh_size; for (i = 1; i < shdr_num; i++) { @@ -126,19 +151,19 @@ static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patc sec_name = get_string_at(shstrtab, shstrtab_size, shdr->sh_name); if (sec_name == NULL) { - ret = -ENOEXEC; log_err("invalid section name, index=%u\n", i); - goto fail; + ret = -ENOEXEC; + goto out; } sec_name = shstrtab + shdr->sh_name; // no need check - if (shdr->sh_type != SHT_NOBITS && shdr->sh_offset + shdr->sh_size > file_size) { + if (shdr->sh_type != SHT_NOBITS && shdr->sh_offset + shdr->sh_size > meta->size) { log_err("section '%s' offset overflow, index=%u\n", sec_name, i); ret = -ENOEXEC; - goto fail; + goto out; } - sec_data = meta->file_buff + shdr->sh_offset; + sec_data = meta->buff + shdr->sh_offset; switch (shdr->sh_type) { case SHT_PROGBITS: if (strcmp(sec_name, UPATCH_FUNCS_NAME) == 0) { @@ -156,12 +181,12 @@ static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patc if (shdr->sh_entsize != sizeof(Elf_Sym)) { log_err("invalid section '%s' entity size\n", sec_name); ret = -ENOEXEC; - goto fail; + goto out; } if (shdr->sh_link > shdr_num) { - ret = -ENOEXEC; log_err("invalid section '%s' string table index\n", sec_name); - goto fail; + ret = -ENOEXEC; + goto out; } if (strcmp(sec_name, SYMTAB_NAME) == 0) { meta->symtab_index = i; @@ -174,7 +199,7 @@ static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patc if (shdr->sh_entsize != sizeof(Elf_Rela)) { log_err("invalid section '%s' entity size\n", sec_name); ret = -ENOEXEC; - goto fail; + goto out; } if (strcmp(sec_name, UPATCH_FUNCS_RELA_NAME) == 0) { meta->rela_index = i; @@ -193,12 +218,12 @@ static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patc if (!meta->symtab_index || !meta->strtab_index) { log_err("patch contains no symbol\n"); ret = -ENOEXEC; - goto fail; + goto out; } if (!meta->func_index || !meta->funcs || !meta->func_num) { log_err("patch contains no function\n"); ret = -ENOEXEC; - goto fail; + goto out; } if (!meta->rela_index || !meta->string_index || !relas || !meta->strings || @@ -206,103 +231,68 @@ static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patc meta->func_num != rela_num) { log_err("invalid patch format\n"); ret = -ENOEXEC; - goto fail; + goto out; } for (i = 0; i < rela_num; i++) { meta->funcs[i].name_off = relas[i].name.r_addend; } - return 0; - -fail: - clear_patch_metadata(meta); +out: + if (file) { + filp_close(file, NULL); + } + if (ret) { + destroy_patch_metadata(meta); + } return ret; } static int resolve_patch_entity(struct patch_entity *patch, const char *file_path) { - int ret = 0; - - struct file *file; + int ret; - INIT_HLIST_NODE(&patch->node); - INIT_LIST_HEAD(&patch->patch_node); - INIT_LIST_HEAD(&patch->actived_node); - - file = filp_open(file_path, O_RDONLY, 0); - if (IS_ERR(file)) { - log_err("failed to open '%s'\n", file_path); - return PTR_ERR(file); - } - - patch->inode = igrab(file_inode(file)); - if (!patch->inode) { - log_err("file '%s' inode is invalid\n", file_path); - ret = -ENOENT; - goto out; - } - - patch->path = kstrdup(file_path, GFP_KERNEL); - if (!patch->path) { - ret = -ENOMEM; - iput(patch->inode); - log_err("faild to alloc filename\n"); - goto out; - } - - ret = resolve_patch_metadata(&patch->meta, file); + ret = resolve_patch_metadata(&patch->meta, file_path); if (ret) { - iput(patch->inode); - KFREE_CLEAR(patch->path); - goto out; + return ret; } - patch->status = UPATCH_STATUS_DEACTIVED; + INIT_HLIST_NODE(&patch->table_node); + patch->target = NULL; + patch->status = UPATCH_STATUS_NOT_APPLIED; -out: - filp_close(file, NULL); - return ret; -} + init_rwsem(&patch->action_rwsem); + INIT_LIST_HEAD(&patch->loaded_node); + INIT_LIST_HEAD(&patch->actived_node); -static struct patch_entity *get_patch_entity_by_inode(struct inode *inode) -{ - struct patch_entity *patch; - struct patch_entity *found = NULL; - - mutex_lock(&g_patch_table_lock); - hash_for_each_possible(g_patch_table, patch, node, inode->i_ino) { - if (patch->inode == inode) { - found = patch; - break; - } - } - mutex_unlock(&g_patch_table_lock); - return found; + return 0; } -/* public interface */ -struct patch_entity *get_patch_entity(const char *path) +static void destroy_patch_entity(struct patch_entity *patch) { - struct patch_entity *patch; - struct inode *inode; + destroy_patch_metadata(&patch->meta); - inode = get_path_inode(path); - if (!inode) { - log_err("failed to get '%s' inode\n", path); - return NULL; - } + WARN_ON(!hlist_unhashed(&patch->table_node)); - patch = get_patch_entity_by_inode(inode); - iput(inode); + patch->target = NULL; + patch->status = UPATCH_STATUS_NOT_APPLIED; - return patch; + WARN_ON(!list_empty(&patch->loaded_node)); + WARN_ON(!list_empty(&patch->actived_node)); + INIT_HLIST_NODE(&patch->table_node); + INIT_LIST_HEAD(&patch->loaded_node); + INIT_LIST_HEAD(&patch->actived_node); } +/* public interface */ struct patch_entity *new_patch_entity(const char *file_path) { - int ret = 0; struct patch_entity *patch = NULL; + int ret; + + if (unlikely(!file_path)) { + return ERR_PTR(-EINVAL); + } patch = kzalloc(sizeof(struct patch_entity), GFP_KERNEL); if (!patch) { @@ -316,41 +306,16 @@ struct patch_entity *new_patch_entity(const char *file_path) return ERR_PTR(ret); } - mutex_lock(&g_patch_table_lock); - hash_add(g_patch_table, &patch->node, patch->inode->i_ino); - mutex_unlock(&g_patch_table_lock); - return patch; } void free_patch_entity(struct patch_entity *patch) { - if (!patch) { + if (unlikely(!patch)) { return; } - log_debug("free patch '%s'\n", patch->path); - - iput(patch->inode); - KFREE_CLEAR(patch->path); - clear_patch_metadata(&patch->meta); - - list_del(&patch->actived_node); - list_del(&patch->patch_node); - hash_del(&patch->node); - + log_debug("free patch '%s'\n", patch->meta.path); + destroy_patch_entity(patch); kfree(patch); } - -void __exit report_patch_table_populated(void) -{ - struct patch_entity *patch; - int bkt; - - mutex_lock(&g_patch_table_lock); - hash_for_each(g_patch_table, bkt, patch, node) { - log_warn("found patch '%s' (%s) on exit", - patch->path ? patch->path : "(null)", patch_status_str(patch->status)); - } - mutex_unlock(&g_patch_table_lock); -} diff --git a/upatch-manage/patch_entity.h b/upatch-manage/patch_entity.h index 02242d36..c7b7e07d 100644 --- a/upatch-manage/patch_entity.h +++ b/upatch-manage/patch_entity.h @@ -78,8 +78,11 @@ struct upatch_function { /* Patch metadata */ struct patch_metadata { - void *file_buff; - loff_t file_size; + const char *path; // patch file path + struct inode *inode; // patch file inode + + void *buff; // patch file buff + loff_t size; // patch file size Elf_Half shstrtab_index; // section '.shstrtab' index Elf_Half symtab_index; // section '.symtab' index @@ -101,25 +104,29 @@ struct patch_metadata { /* Patch entity */ struct patch_entity { - const char *path; // patch file path - struct inode *inode; // patch file inode - - struct patch_metadata meta; // patch elf metadata - struct target_entity *target; // patch target + struct patch_metadata meta; // patch file metadata + struct hlist_node table_node; // global patch hash table node - enum upatch_status status; // patch status + struct rw_semaphore action_rwsem; // patch action rw semaphore + struct target_entity *target; // patch target + enum upatch_status status; // patch status - struct hlist_node node; // all patches store in hash table - struct list_head patch_node; // patch node in target entity - struct list_head actived_node; // actived patch in target entity + struct list_head loaded_node; // target loaded patch node + struct list_head actived_node; // target actived patch list node }; -struct patch_entity *get_patch_entity(const char *patch_file); - -struct patch_entity *new_patch_entity(const char *patch_file); +/* + * Load a patch file + * @param file_path: patch file path + * @return patch entity + */ +struct patch_entity *new_patch_entity(const char *file_path); +/* + * Free a patch entity + * @param patch: patch entity + * @return void + */ void free_patch_entity(struct patch_entity *patch); -void __exit report_patch_table_populated(void); - #endif // _UPATCH_MANAGE_PATCH_ENTITY_H diff --git a/upatch-manage/patch_load.c b/upatch-manage/patch_load.c index 8b622e61..85727c3c 100644 --- a/upatch-manage/patch_load.c +++ b/upatch-manage/patch_load.c @@ -25,10 +25,11 @@ #include "arch/patch_load.h" -#include "patch_entity.h" #include "target_entity.h" #include "process_entity.h" +#include "patch_entity.h" #include "symbol_resolve.h" + #include "kernel_compat.h" #include "util.h" @@ -120,13 +121,13 @@ static void layout_sections(struct patch_context *ctx) static int init_load_info(struct patch_context *ctx, const struct patch_entity *patch, const struct target_entity *target, unsigned long vma_start) { - void *file_buff = patch->meta.file_buff; - loff_t file_size = patch->meta.file_size; + void *file_buff = patch->meta.buff; + loff_t file_size = patch->meta.size; ctx->target = &target->meta; ctx->patch = &patch->meta; ctx->load_bias = vma_start - target->meta.vma_offset; - log_debug("process %d: vma_start=0x%lx, load_bias=0x%lx\n", task_pid_nr(current), vma_start, ctx->load_bias); + log_debug("process %d: vma_start=0x%lx, load_bias=0x%lx\n", task_tgid_nr(current), vma_start, ctx->load_bias); // alloc & copy whole patch into kernel temporarily ctx->buff = vmalloc(file_size); @@ -479,52 +480,6 @@ static int set_memory_privileges(const struct patch_context *ctx) return 0; } -static int set_execution_map(const struct patch_context *ctx, - struct process_entity *process, struct patch_entity *patch) -{ - struct upatch_function *funcs = (struct upatch_function *)ctx->func_shdr->sh_addr; - size_t func_num = ctx->func_shdr->sh_size / (sizeof (struct upatch_function)); - - struct upatch_relocation *relas = (struct upatch_relocation *)ctx->rela_shdr->sh_addr; - const char *strings = (const char *)ctx->string_shdr->sh_addr; - - size_t i; - struct upatch_function *func; - const char *func_name; - - struct patch_info *info; - struct pc_pair *entry; - - info = kzalloc(sizeof(struct patch_info), GFP_KERNEL); - if (!info) { - log_err("failed to alloc patch info\n"); - return -ENOMEM; - } - - hash_init(info->pc_maps); - for (i = 0; i < func_num; ++i) { - func = &funcs[i]; - - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) { - free_patch_info(info); - return -ENOMEM; - } - - func_name = strings + relas[i].name.r_addend; - entry->old_pc = funcs[i].old_addr + ctx->load_bias + ctx->target->load_offset; - entry->new_pc = funcs[i].new_addr; - hash_add(info->pc_maps, &entry->node, entry->old_pc); - log_debug("function: 0x%08lx -> 0x%08lx, name: '%s'\n", entry->old_pc, entry->new_pc, func_name); - } - - list_add(&info->list, &process->loaded_patches); - info->patch = patch; - process->active_info = info; - - return 0; -} - /* The main idea is from insmod */ int upatch_resolve(struct target_entity *target, struct patch_entity *patch, struct process_entity *process, unsigned long vma_start) @@ -564,7 +519,7 @@ int upatch_resolve(struct target_entity *target, struct patch_entity *patch, str goto fail; } - ret = set_execution_map(&context, process, patch); + ret = process_write_patch_info(process, patch, &context); if (ret) { goto fail; } diff --git a/upatch-manage/patch_manage.c b/upatch-manage/patch_manage.c index 47d883b4..ab2455c8 100644 --- a/upatch-manage/patch_manage.c +++ b/upatch-manage/patch_manage.c @@ -23,498 +23,692 @@ #include #include -#include "patch_entity.h" +#include +#include + #include "target_entity.h" #include "process_entity.h" +#include "patch_entity.h" #include "patch_load.h" #include "util.h" +/* + * ===================================================================== + * LOCKING HIERARCHY + * ===================================================================== + * To prevent deadlocks, the following lock acquisition order MUST be + * strictly followed throughout the entire module: + * + * 1. g_global_table_rwsem + * 2. target_entity->action_rwsem + * 3. patch_entity->action_rwsem + * + * Any deviation from this order will lead to deadlocks. + * ===================================================================== + */ + +#define UPROBE_RUN_OLD_FUNC 0 + #ifndef UPROBE_ALTER_PC #define UPROBE_ALTER_PC 2 #endif -#define UPROBE_RUN_OLD_FUNC 0 +#define PATCH_TABLE_HASH_BITS 4 +#define TARGET_TABLE_HASH_BITS 4 + +static DEFINE_HASHTABLE(g_patch_table, PATCH_TABLE_HASH_BITS); +static DEFINE_HASHTABLE(g_target_table, TARGET_TABLE_HASH_BITS); -static int jump_to_new_pc(struct pt_regs *regs, struct patch_info *info, unsigned long old_pc) +static DECLARE_RWSEM(g_global_table_rwsem); + +static int upatch_uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs); + +static struct uprobe_consumer g_upatch_consumer = { + .handler = upatch_uprobe_handler, +}; + +/* GLOBAL ENTITY HASH TABLE */ +static struct patch_entity *find_patch_by_inode(struct inode *inode) { - struct pc_pair *pp; + struct patch_entity *patch; - bool find = false; - hash_for_each_possible(info->pc_maps, pp, node, old_pc) { - if (pp->old_pc == old_pc) { - find = true; - break; + hash_for_each_possible(g_patch_table, patch, table_node, inode->i_ino) { + if (patch->meta.inode == inode) { + return patch; } } - if (!find) { - log_err("cannot find new pc for 0x%lx\n", old_pc); - return UPROBE_RUN_OLD_FUNC; - } - - log_debug("jump from 0x%lx -> 0x%lx\n", old_pc, pp->new_pc); - - instruction_pointer_set(regs, pp->new_pc); - return UPROBE_ALTER_PC; + return NULL; } -static struct file *get_target_file_from_pc(struct mm_struct *mm, unsigned long pc, unsigned long *code_start) +struct patch_entity *find_patch(const char *path) { - struct vm_area_struct *vma = NULL; - - vma = find_vma(mm, pc); - if (!vma) { - return NULL; - } + struct patch_entity *patch; + struct inode *inode; - if (!vma->vm_file) { + inode = get_path_inode(path); + if (!inode) { + log_err("failed to get '%s' inode\n", path); return NULL; } - *code_start = vma->vm_start; + patch = find_patch_by_inode(inode); - return vma->vm_file; + iput(inode); + return patch; } -static struct target_entity *get_target_from_pc(unsigned long pc, unsigned long *code_start) +static struct target_entity *find_target_by_inode(struct inode *inode) { struct target_entity *target; - struct mm_struct *mm = current->mm; - struct file *target_file; - - mmap_read_lock(mm); - target_file = get_target_file_from_pc(mm, pc, code_start); - mmap_read_unlock(mm); - - if (!target_file) { - log_err("no backen file found for upatch\n"); - return NULL; + hash_for_each_possible(g_target_table, target, table_node, inode->i_ino) { + if (target->meta.inode == inode) { + return target; + } } - get_file(target_file); - target = get_target_entity_by_inode(file_inode(target_file)); - fput(target_file); + return NULL; +} + +static struct target_entity *find_target(const char *path) +{ + struct target_entity *target; + struct inode *inode; - if (!target) { - log_err("no target found in patch handler\n"); + inode = get_path_inode(path); + if (!inode) { + log_err("failed to get '%s' inode\n", path); return NULL; } + target = find_target_by_inode(inode); + + iput(inode); return target; } -static struct patch_info* find_loaded_patches(struct process_entity *pro, struct patch_entity *patch) +/* UPROBE IMPLEMENTATION */ +static struct inode *find_vma_file_inode(unsigned long pc, unsigned long *vma_start) { - struct patch_info* info; - list_for_each_entry(info, &pro->loaded_patches, list) { - if (info->patch == patch) { - return info; - } - } - return NULL; -} + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; -// UPROBE_RUN_OLD_FUNC means run old func -// UPROBE_ALTER_PC means run new func -static int upatch_uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs) -{ - unsigned long pc = instruction_pointer(regs); - struct target_entity *target; - struct patch_entity *actived_patch; - struct process_entity *process; - int ret = UPROBE_RUN_OLD_FUNC; - const char *name = current->comm; - pid_t pid = task_pid_nr(current); - pid_t tgid = task_tgid_nr(current); - unsigned long target_code_start; + struct file *file = NULL; + struct inode *inode = NULL; - log_debug("upatch handler triggered on process '%s' (pid=%d, tgid=%d, pc=0x%lx)\n", name, pid, tgid, pc); + mmap_read_lock(mm); - target = get_target_from_pc(pc, &target_code_start); - if (!target) { - log_err("cannot find target entity of '%s'\n", name); - return ret; + vma = find_vma(mm, pc); + if (likely(vma && vma->vm_file)) { + *vma_start = vma->vm_start; + file = vma->vm_file; + get_file(file); // get_file allows NULL pointer } - down_read(&target->patch_lock); - actived_patch = list_first_entry(&target->actived_patch_list, struct patch_entity, actived_node); - up_read(&target->patch_lock); + mmap_read_unlock(mm); - if (!actived_patch) { - log_err("cannot find any actived patch of '%s'\n", name); - return ret; + if (unlikely(!file)) { + log_err("cannot find vma file on pc 0x%lx\n", pc); + goto out; } - process = get_process(target); - if (!process) { - return UPROBE_RUN_OLD_FUNC; + inode = igrab(file_inode(file)); + if (unlikely(!inode)) { + log_err("cannot find vma file inode on pc 0x%lx\n", pc); + goto out; } - // multi thread may trap at the same time, only one thread can load patch, other thread should wait - mutex_lock(&process->lock); +out: + if (likely(file)) { + fput(file); + } + return inode; +} - if (!process->active_info) { - log_debug("applying new patch '%s' to '%s' (pid=%d, tgid=%d)...\n", - actived_patch->path, name, pid, tgid); - ret = upatch_resolve(target, actived_patch, process, target_code_start); - if (ret) { - log_err("failed to apply patch '%s' to '%s', ret=%d\n", actived_patch->path, name, ret); - goto fail; - } - } else if (process->active_info->patch != actived_patch) { - struct patch_info* info = find_loaded_patches(process, actived_patch); - if (info) { - process->active_info = info; - goto ok; - } +static int jump_to_new_pc(struct pt_regs *regs, const struct patch_info *patch, unsigned long pc) +{ + struct pc_pair *pair; + struct pc_pair *found_pair = NULL; - log_debug("applying latest patch '%s' to '%s' (pid=%d, tgid=%d)...\n", - actived_patch->path, name, pid, tgid); - ret = upatch_resolve(target, actived_patch, process, target_code_start); - if (ret) { - log_err("failed to apply patch '%s' to '%s', ret=%d\n", actived_patch->path, name, ret); - goto fail; + hash_for_each_possible(patch->pc_maps, pair, node, pc) { + if (pair->old_pc == pc) { + found_pair = pair; + break; } } -ok: - ret = jump_to_new_pc(regs, process->active_info, pc); - mutex_unlock(&process->lock); - return ret; + if (unlikely(!found_pair)) { + log_err("cannot find new pc for 0x%lx\n", pc); + return UPROBE_RUN_OLD_FUNC; + } -fail: - mutex_unlock(&process->lock); - return UPROBE_RUN_OLD_FUNC; -} + log_debug("jump from 0x%lx -> 0x%lx\n", pc, found_pair->new_pc); + instruction_pointer_set(regs, found_pair->new_pc); -static struct uprobe_consumer patch_consumer = { - .handler = upatch_uprobe_handler, -}; + return UPROBE_ALTER_PC; +} -// register uprobe if offset of this target have no new function -static int target_register_function(struct target_entity *target, loff_t offset, - struct upatch_function *func) +static void free_exited_process(struct list_head *process_list) { - struct patched_offset *off = NULL; - struct patched_func_node *func_node; - bool find = false; - int ret; + struct process_entity *process; + struct process_entity *tmp; - func_node = kzalloc(sizeof(struct patched_func_node), GFP_KERNEL); - if (!func_node) { - return -ENOMEM; + if (unlikely(!process_list)) { + return; } - // find if this target have func changed in offset - list_for_each_entry(off, &target->offset_node, list) { - if (off->offset == offset) { - find = true; - break; - } + list_for_each_entry_safe(process, tmp, process_list, process_node) { + list_del_init(&process->process_node); + free_process(process); } +} - // This is the first func in this offset, so we should create a off node - if (!find) { - off = kzalloc(sizeof(struct patched_offset), GFP_KERNEL); - if (!off) { - kfree(func_node); - return -ENOMEM; - } +static int upatch_uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs) +{ + const char *name = current->comm; + pid_t pid = task_pid_nr(current); + pid_t tgid = task_tgid_nr(current); + unsigned long pc = instruction_pointer(regs); - off->offset = offset; - INIT_LIST_HEAD(&off->funcs_head); + struct inode *inode; + unsigned long vma_start; - log_debug("register uprobe on '%s' (inode: %lu) at 0x%llx\n", - target->path, target->inode->i_ino, offset); - ret = uprobe_register(target->inode, offset, &patch_consumer); - if (ret) { - log_err("failed to register uprobe on '%s' (inode: %lu) at 0x%llx, ret=%d\n", - target->path, target->inode->i_ino, offset, ret); - kfree(func_node); - kfree(off); - return ret; - } + struct target_entity *target; + struct patch_entity *latest_patch; - list_add(&off->list, &target->offset_node); - } + struct list_head exited_proc_list = LIST_HEAD_INIT(exited_proc_list); + struct process_entity *process; + struct patch_info *loaded_patch; - func_node->func = func; - list_add(&func_node->list, &off->funcs_head); - return 0; -} + int ret = UPROBE_RUN_OLD_FUNC; -// unregister uprobe if offset of this target have no new function -static void unregister_function_uprobe(struct target_entity *target, loff_t offset, struct upatch_function *func) -{ - struct patched_offset *off = NULL; - struct patched_func_node *func_node = NULL; - struct patched_func_node *tmp = NULL; - bool find = false; - - list_for_each_entry(off, &target->offset_node, list) { - if (off->offset == offset) { - find = true; - break; - } - } + log_debug("upatch handler triggered on process '%s' (pid=%d, tgid=%d, pc=0x%lx)\n", name, pid, tgid, pc); - if (!find) { - log_err("cannot find offest 0x%llx of '%s'\n", offset, target->path); - return; + /* find vma file and inode out of the lock */ + inode = find_vma_file_inode(pc, &vma_start); + if (unlikely(!inode)) { + log_err("cannot find vma file inode in '%s'\n", name); + return UPROBE_RUN_OLD_FUNC; } - // There may be multiple version func in the same offset of a target. We should find it and delete it - list_for_each_entry_safe(func_node, tmp, &off->funcs_head, list) { - if (func_node->func == func) { - list_del(&func_node->list); - kfree(func_node); + down_read(&g_global_table_rwsem); + + /* step 1. find target entity by vma file inode */ + target = find_target_by_inode(inode); + iput(inode); + inode = NULL; + + if (unlikely(!target)) { + log_err("cannot find target entity of '%s'\n", name); + ret = UPROBE_RUN_OLD_FUNC; + goto unlock_global_table; + } + + /* step 2. find target latest patch */ + latest_patch = list_first_entry_or_null(&target->actived_list, struct patch_entity, actived_node); + if (unlikely(!latest_patch)) { + log_err("target '%s' has no patch\n", target->meta.path); + ret = UPROBE_RUN_OLD_FUNC; + goto unlock_global_table; + } + + mutex_lock(&target->process_mutex); + + /* step 3. collect all exited process */ + target_gather_exited_processes(target, &exited_proc_list); + + /* step 4. find or create process entity for current process */ + process = target_get_or_create_process(target); + if (unlikely(!process)) { + log_err("failed to get process of '%s'\n", target->meta.path); + ret = UPROBE_RUN_OLD_FUNC; + mutex_unlock(&target->process_mutex); + goto unlock_global_table; + } + + mutex_unlock(&target->process_mutex); + + /* step 5. check if we need resolve patch on the process */ + mutex_lock(&process->lock); // we want to ensure only one thread can resolve patch + + if (!process->latest_patch || process->latest_patch->patch != latest_patch) { + loaded_patch = process_find_loaded_patch(process, latest_patch); + if (loaded_patch) { + log_debug("switch patch to '%s'\n", latest_patch->meta.path); + process->latest_patch = loaded_patch; + } else { + log_debug("applying patch '%s' to process '%s' (pid=%d)\n", latest_patch->meta.path, name, pid); + ret = upatch_resolve(target, latest_patch, process, vma_start); + if (ret) { + log_err("failed to apply patch '%s' to process '%s', ret=%d\n", latest_patch->meta.path, name, ret); + ret = UPROBE_RUN_OLD_FUNC; + goto unlock_process; + } } } - if (list_empty(&off->funcs_head)) { - uprobe_unregister(target->inode, offset, &patch_consumer); - list_del(&off->list); - kfree(off); - } + /* search and set pc register to new address */ + ret = jump_to_new_pc(regs, process->latest_patch, pc); + +unlock_process: + mutex_unlock(&process->lock); + +unlock_global_table: + up_read(&g_global_table_rwsem); + + free_exited_process(&exited_proc_list); + return ret; } -static void target_unregister_functions(struct target_entity *target, struct patch_entity *patch, - struct upatch_function *funcs, size_t count) +/* PATCH MANAGEMENT */ +static void unregister_single_patch_function(struct target_entity *target, struct upatch_function *func) { - struct upatch_function *func = NULL; - size_t i = 0; - loff_t offset = 0; - const char *name = NULL; - - log_debug("unregister patch '%s' functions:\n", target->path); - for (i = 0; i < count; i++) { - func = &funcs[i]; - offset = func->old_addr; - name = patch->meta.strings + func->name_off; + bool need_unregister = false; - log_debug("- function: offset=0x%08llx, size=0x%04llx, name='%s'\n", offset, func->old_size, name); - unregister_function_uprobe(target, offset, func); + target_remove_function(target, func, &need_unregister); + if (need_unregister) { + uprobe_unregister(target->meta.inode, func->old_addr, &g_upatch_consumer); } } -// patch will be actived in uprobe handler -static int do_active_patch(struct target_entity *target, struct patch_entity *patch) +static void unregister_patch_functions(struct target_entity *target, struct patch_entity *patch, size_t count) { struct upatch_function *funcs = patch->meta.funcs; + const char *strings = patch->meta.strings; + struct upatch_function *func; - int ret = 0; - size_t i = 0; - loff_t offset; - const char *name = NULL; + const char *name; + size_t i; - log_debug("register target '%s' functions:\n", target->path); - down_write(&target->patch_lock); + if (count > patch->meta.func_num) { + log_err("function count %zu exceeds %zu\n", count, patch->meta.func_num); + return; + } - for (i = 0; i < patch->meta.func_num; i++) { + log_debug("unregister patch '%s' functions:\n", target->meta.path); + for (i = 0; i < count; i++) { func = &funcs[i]; - offset = func->old_addr; - name = patch->meta.strings + func->name_off; + name = strings + func->name_off; - log_debug("+ function: offset=0x%08llx, size=0x%04llx, name='%s'\n", offset, func->old_size, name); - ret = target_register_function(target, offset, func); - if (ret) { - log_err("failed to register function '%s', ret=%d\n", name, ret); - target_unregister_functions(target, patch, funcs, i); - goto out; - } + log_debug("- function: offset=0x%08llx, size=0x%04llx, name='%s'\n", func->old_addr, func->old_size, name); + unregister_single_patch_function(target, func); } - - list_add(&patch->actived_node, &target->actived_patch_list); - patch->status = UPATCH_STATUS_ACTIVED; - -out: - up_write(&target->patch_lock); - - return ret; } -static void target_remove_actived_patch(struct target_entity *target, struct patch_entity *patch) +static int register_single_patch_function(struct target_entity *target, struct upatch_function *func) { - struct patch_entity *p = NULL; - struct patch_entity *tmp = NULL; - bool found = false; - - list_for_each_entry_safe(p, tmp, &target->actived_patch_list, actived_node) { - if (p == patch) { - list_del_init(&p->actived_node); - found = true; - break; - } + bool need_register = false; + int ret; + + ret = target_add_function(target, func, &need_register); + if (ret) { + log_err("failed to add patch function to target\n"); + return ret; } - if (!found) { - log_err("cannot find actived patch '%s'\n", patch->path); + if (need_register) { + ret = uprobe_register(target->meta.inode, func->old_addr, &g_upatch_consumer); + if (ret) { + target_remove_function(target, func, &need_register); // rollback, remove function from target + log_err("failed to register uprobe on '%s' (inode: %lu) at 0x%llx, ret=%d\n", + target->meta.path, target->meta.inode->i_ino, func->old_addr, ret); + return ret; + } } + + return 0; } -// delete patch inode & function in target -// patch will be deactived in uprobe handler -static void do_deactive_patch(struct patch_entity *patch) +static int register_patch_functions(struct target_entity *target, struct patch_entity *patch, size_t count) { - struct target_entity *target = patch->target; struct upatch_function *funcs = patch->meta.funcs; + const char *strings = patch->meta.strings; + + struct upatch_function *func; + const char *name; + size_t i; - down_write(&target->patch_lock); + int ret; - target_unregister_functions(target, patch, funcs, patch->meta.func_num); - target_remove_actived_patch(target, patch); - patch->status = UPATCH_STATUS_DEACTIVED; + if (count > patch->meta.func_num) { + log_err("function count %zu exceeds %zu\n", count, patch->meta.func_num); + return -EINVAL; + } + + log_debug("register target '%s' functions:\n", target->meta.path); + for (i = 0; i < count; i++) { + func = &funcs[i]; + name = strings + func->name_off; - up_write(&target->patch_lock); + log_debug("+ function: offset=0x%08llx, size=0x%04llx, name='%s'\n", func->old_addr, func->old_size, name); + ret = register_single_patch_function(target, func); + if (ret) { + log_err("failed to register function '%s'\n", name); + unregister_patch_functions(target, patch, i); + return ret; + } + } + + return 0; } /* public interface */ enum upatch_status upatch_status(const char *patch_file) { - struct patch_entity *patch; + enum upatch_status status = UPATCH_STATUS_NOT_APPLIED; + struct patch_entity *patch = NULL; - patch = get_patch_entity(patch_file); - return patch ? patch->status : UPATCH_STATUS_NOT_APPLIED; + down_read(&g_global_table_rwsem); + patch = find_patch(patch_file); + if (patch) { + status = patch->status; + } + up_read(&g_global_table_rwsem); + + return status; } -int upatch_load(const char *patch_file, const char *target_path) +int upatch_load(const char *patch_file, const char *target_file) { struct patch_entity *patch = NULL; + struct patch_entity *preload_patch = NULL; + struct patch_entity *patch_to_free = NULL; struct target_entity *target = NULL; + struct target_entity *preload_target = NULL; + struct target_entity *target_to_free = NULL; + int ret = 0; - if (!patch_file || !target_path) { + if (!patch_file || !target_file) { return -EINVAL; } - log_debug("loading patch '%s' -> '%s'...\n", patch_file, target_path); + log_debug("loading patch '%s' -> '%s'...\n", patch_file, target_file); - patch = get_patch_entity(patch_file); - if (patch) { - log_err("patch '%s' is already loaded\n", patch_file); - return -EEXIST; + /* fast path, return if patch already exists */ + down_read(&g_global_table_rwsem); + if (find_patch(patch_file)) { + log_err("patch '%s' is already exist\n", patch_file); + ret = -EEXIST; + up_read(&g_global_table_rwsem); + goto out; } + up_read(&g_global_table_rwsem); - patch = new_patch_entity(patch_file); - if (IS_ERR(patch)) { + /* preload patch & target file out of the lock */ + preload_patch = new_patch_entity(patch_file); + if (IS_ERR(preload_patch)) { log_err("failed to load patch '%s'\n", patch_file); - return PTR_ERR(patch); + ret = PTR_ERR(preload_patch); + goto out_free; + } + + preload_target = new_target_entity(target_file); + if (IS_ERR(preload_target)) { + log_err("failed to load target '%s'\n", target_file); + patch_to_free = preload_patch; + ret = PTR_ERR(preload_target); + goto out_free; } - target = get_target_entity(target_path); + /* slow path, load patch & target from file */ + down_write(&g_global_table_rwsem); + + /* step 1. recheck patch and target reference */ + patch = find_patch(patch_file); + if (!patch) { + patch = preload_patch; // patch does not exist, use preloaded patch + } else { + log_err("patch '%s' is already exist\n", patch_file); + ret = -EEXIST; + patch_to_free = preload_patch; + target_to_free = preload_target; + goto unlock_global_table; + } + + target = find_target(target_file); if (!target) { - target = new_target_entity(target_path); - if (IS_ERR(target)) { - free_patch_entity(patch); - log_err("failed to load target '%s'\n", target_path); - return PTR_ERR(target); - } + target = preload_target; // target does not exist, use preloaded target + } else { + target_to_free = preload_target; // target exists, need free preload one } - list_add(&patch->patch_node, &target->all_patch_list); + if (target != preload_target) { + down_write(&target->action_rwsem); // lock global target, patch is always local + } + + /* step 2. add patch to global patch table */ + hash_add(g_patch_table, &patch->table_node, patch->meta.inode->i_ino); + + /* step 3. add patch to target all patches list */ + list_add(&patch->loaded_node, &target->loaded_list); + + /* step 4. update patch status */ patch->target = target; patch->status = UPATCH_STATUS_DEACTIVED; - log_debug("patch '%s' is loaded\n", patch_file); - return 0; + if (target != preload_target) { + up_write(&target->action_rwsem); // unlock global target, patch is always local + } else { + /* step 5. add new target to global target table */ + hash_add(g_target_table, &target->table_node, target->meta.inode->i_ino); + } + +unlock_global_table: + up_write(&g_global_table_rwsem); + +out_free: + if (patch_to_free) { + free_patch_entity(patch_to_free); + } + if (target_to_free) { + free_target_entity(target_to_free); + } + +out: + if (!ret) { + log_debug("patch '%s' is loaded\n", patch_file); + } + return ret; } int upatch_remove(const char *patch_file) { struct patch_entity *patch = NULL; struct target_entity *target = NULL; + struct patch_entity *patch_to_free = NULL; + struct target_entity *target_to_free = NULL; + + int ret = 0; log_debug("removing patch '%s'...\n", patch_file); - patch = get_patch_entity(patch_file); + down_write(&g_global_table_rwsem); + + patch = find_patch(patch_file); if (!patch) { - log_err("cannot find patch entity '%s'\n", patch_file); - return -ENOENT; + log_err("cannot find patch entity\n"); + ret = -ENOENT; + goto unlock_global_table; } if (patch->status != UPATCH_STATUS_DEACTIVED) { log_err("invalid patch status\n"); - return -EPERM; + ret = -EPERM; + goto unlock_global_table; } target = patch->target; + if (!target) { + log_err("cannot find target entity\n"); + ret = -EFAULT; + goto unlock_global_table; + } - free_patch_entity(patch); - if (!is_target_has_patch(target)) { - free_target_entity(target); + down_write(&target->action_rwsem); + down_write(&patch->action_rwsem); + + /* step 1. remove patch from from global table */ + hash_del(&patch->table_node); + + /* step 2. remove patch from target patch list */ + list_del_init(&patch->loaded_node); + + /* step 3. check & remove target form global table */ + if (list_empty(&target->loaded_list)) { + hash_del(&target->table_node); + target_to_free = target; } - log_debug("patch '%s' is removed\n", patch_file); - return 0; + /* step 4. update patch status */ + patch->target = NULL; + patch->status = UPATCH_STATUS_NOT_APPLIED; + patch_to_free = patch; + + up_write(&patch->action_rwsem); + up_write(&target->action_rwsem); + +unlock_global_table: + up_write(&g_global_table_rwsem); + + if (patch_to_free) { + free_patch_entity(patch_to_free); + } + if (target_to_free) { + free_target_entity(target_to_free); + } + if (!ret) { + log_debug("patch '%s' is removed\n", patch_file); + } + return ret; } int upatch_active(const char *patch_file) { struct patch_entity *patch = NULL; struct target_entity *target = NULL; - int ret; + int ret = 0; log_debug("activating patch '%s'...\n", patch_file); - patch = get_patch_entity(patch_file); + down_read(&g_global_table_rwsem); + + patch = find_patch(patch_file); if (!patch) { - log_err("cannot find patch entity '%s'\n", patch_file); - return -ENOENT; + log_err("cannot find patch entity\n"); + ret = -ENOENT; + goto unlock_global_table; } - // check patch status if (patch->status != UPATCH_STATUS_DEACTIVED) { log_err("invalid patch status\n"); - return -EPERM; + ret = -EPERM; + goto unlock_global_table; } target = patch->target; + if (!target) { + log_err("cannot find target entity\n"); + ret = -EFAULT; + goto unlock_global_table; + } + + down_write(&target->action_rwsem); + down_write(&patch->action_rwsem); - ret = do_active_patch(target, patch); + /* step 1. register patch functions to target */ + ret = register_patch_functions(target, patch, patch->meta.func_num); if (ret) { - log_err("failed to active patch '%s', ret=%d\n", patch_file, ret); - return ret; + log_err("failed to register patch functions\n"); + goto unlock_entity; } - log_debug("patch '%s' is actived\n", patch_file); - return 0; + /* step 2. add patch to target actived patch list */ + list_add(&patch->actived_node, &target->actived_list); + + /* step 3. update patch status */ + patch->status = UPATCH_STATUS_ACTIVED; + +unlock_entity: + up_write(&patch->action_rwsem); + up_write(&target->action_rwsem); + +unlock_global_table: + up_read(&g_global_table_rwsem); + + if (!ret) { + log_debug("patch '%s' is actived\n", patch_file); + } + return ret; } int upatch_deactive(const char *patch_file) { struct patch_entity *patch = NULL; + struct target_entity *target = NULL; + int ret = 0; log_debug("deactivating patch '%s'...\n", patch_file); - // find patch - patch = get_patch_entity(patch_file); + down_read(&g_global_table_rwsem); + + patch = find_patch(patch_file); if (!patch) { - log_err("cannot find patch entity '%s'\n", patch_file); - return -ENOENT; + log_err("cannot find patch entity\n"); + ret = -ENOENT; + goto unlock_global_table; } - // check patch status if (patch->status != UPATCH_STATUS_ACTIVED) { log_err("invalid patch status\n"); - return -EPERM; + ret = -EPERM; + goto unlock_global_table; + } + + target = patch->target; + if (!target) { + log_err("cannot find target entity\n"); + ret = -EFAULT; + goto unlock_global_table; } - do_deactive_patch(patch); + down_write(&target->action_rwsem); + down_write(&patch->action_rwsem); - log_debug("patch '%s' is deactived\n", patch_file); - return 0; + /* step 1. remove patch functions from target */ + unregister_patch_functions(target, patch, patch->meta.func_num); + + /* step 2. remove patch from target actived patch list */ + list_del_init(&patch->actived_node); + + /* step 3. update patch status */ + patch->status = UPATCH_STATUS_DEACTIVED; + + up_write(&patch->action_rwsem); + up_write(&target->action_rwsem); + +unlock_global_table: + up_read(&g_global_table_rwsem); + + if (!ret) { + log_debug("patch '%s' is deactived\n", patch_file); + } + return ret; } -void target_unregister_uprobes(struct target_entity *target) +void __exit report_global_table_populated(void) { - struct patched_offset *off = NULL; - struct patched_offset *tmp_off = NULL; + struct patch_entity *patch; + struct target_entity *target; + int bkt; - log_debug("unregister '%s' (inode: %lu) uprobes:", target->path, target->inode->i_ino); - list_for_each_entry_safe(off, tmp_off, &target->offset_node, list) { - log_debug("unregister offset 0x%llx\n", off->offset); - uprobe_unregister(target->inode, off->offset, &patch_consumer); - list_del(&off->list); - kfree(off); + down_read(&g_global_table_rwsem); + hash_for_each(g_patch_table, bkt, patch, table_node) { + log_warn("found patch '%s' on exit, status=%s", + patch->meta.path ? patch->meta.path : "(null)", patch_status_str(patch->status)); + } + hash_for_each(g_target_table, bkt, target, table_node) { + log_err("found target '%s' on exit", target->meta.path ? target->meta.path : "(null)"); } + up_read(&g_global_table_rwsem); } diff --git a/upatch-manage/patch_manage.h b/upatch-manage/patch_manage.h index 5392f2dd..4ed63c9d 100644 --- a/upatch-manage/patch_manage.h +++ b/upatch-manage/patch_manage.h @@ -21,6 +21,10 @@ #ifndef _UPATCH_MANAGE_PATCH_MANAGE_H #define _UPATCH_MANAGE_PATCH_MANAGE_H +#include + +struct inode; + enum upatch_status; struct target_entity; @@ -34,6 +38,6 @@ int upatch_active(const char *patch_file); int upatch_deactive(const char *patch_file); -void target_unregister_uprobes(struct target_entity *target); +void __exit report_global_table_populated(void); #endif // _UPATCH_MANAGE_PATCH_MANAGE_H diff --git a/upatch-manage/process_entity.c b/upatch-manage/process_entity.c index d20b2954..d8e397b1 100644 --- a/upatch-manage/process_entity.c +++ b/upatch-manage/process_entity.c @@ -22,111 +22,133 @@ #include +#include "patch_entity.h" #include "target_entity.h" + +#include "patch_load.h" #include "util.h" -void free_patch_info(struct patch_info *info) +static void free_patch_info(struct patch_info *patch) { struct pc_pair *pair; struct hlist_node *tmp; int bkt; - hash_for_each_safe(info->pc_maps, bkt, tmp, pair, node) { + if (unlikely(!patch)) { + return; + } + + hash_for_each_safe(patch->pc_maps, bkt, tmp, pair, node) { hash_del(&pair->node); kfree(pair); } - kfree(info); + kfree(patch); +} + +struct process_entity *new_process(struct target_entity *target) +{ + if (unlikely(!target)) { + return ERR_PTR(-EINVAL); + } + + struct process_entity *process = kzalloc(sizeof(struct process_entity), GFP_KERNEL); + if (!process) { + return ERR_PTR(-ENOMEM); + } + + process->pid = get_task_pid(current, PIDTYPE_TGID); + if (!process->pid) { + log_err("failed to get process %d task pid\n", task_tgid_nr(current)); + kfree(process); + return ERR_PTR(-EFAULT); + } + process->task = get_task_struct(current); + + mutex_init(&process->lock); + + process->latest_patch = NULL; + INIT_LIST_HEAD(&process->loaded_patches); + INIT_LIST_HEAD(&process->process_node); + + return process; } -// process may be exited already, free loaded patch info void free_process(struct process_entity* process) { struct patch_info *info; struct patch_info *tmp; - pid_t pid; - - pid = pid_nr(process->pid_s); - log_debug("free process tgid %d\n", pid); + if (unlikely(!process)) { + return; + } - put_pid(process->pid_s); + log_debug("free process %d\n", task_pid_nr(process->task)); + put_pid(process->pid); + put_task_struct(process->task); - mutex_lock(&process->lock); - list_for_each_entry_safe(info, tmp, &process->loaded_patches, list) { - list_del(&info->list); + list_for_each_entry_safe(info, tmp, &process->loaded_patches, node) { + list_del(&info->node); free_patch_info(info); } - list_del(&process->list); - mutex_unlock(&process->lock); kfree(process); } -// we use struct pid to reference different process -static struct process_entity *do_get_process_and_free_exit_process(struct target_entity *target) +struct patch_info *process_find_loaded_patch(struct process_entity *process, struct patch_entity *patch) { - struct process_entity *process = NULL; - struct process_entity *tmp = NULL; - struct process_entity *res = NULL; - struct pid *pid_s; - struct task_struct *task; - - pid_s = get_task_pid(current, PIDTYPE_TGID); - - list_for_each_entry_safe(process, tmp, &target->process_head, list) { - task = get_pid_task(process->pid_s, PIDTYPE_TGID); - if (!task) { - // old process is exit, so task is NULL, free it - free_process(process); - continue; - } - put_task_struct(task); + struct patch_info *curr_patch; - if (process->pid_s != pid_s) { - continue; + list_for_each_entry(curr_patch, &process->loaded_patches, node) { + if (curr_patch->patch == patch) { + return curr_patch; } - - res = process; - break; } - put_pid(pid_s); - return res; + return NULL; } -static struct process_entity *new_process(struct target_entity *target) +int process_write_patch_info(struct process_entity *process, struct patch_entity *patch, struct patch_context *ctx) { - struct process_entity *process = kzalloc(sizeof(struct process_entity), GFP_KERNEL); - if (!process) { - return NULL; - } + struct upatch_function *funcs = (struct upatch_function *)ctx->func_shdr->sh_addr; + size_t func_num = ctx->func_shdr->sh_size / (sizeof (struct upatch_function)); - log_debug("Create process tgid %d for %s\n", task_tgid_nr(current), target->path); - process->pid_s = get_task_pid(current, PIDTYPE_TGID); - process->task = current; - INIT_LIST_HEAD(&process->loaded_patches); - mutex_init(&process->lock); - list_add(&process->list, &target->process_head); - return process; -} + struct upatch_relocation *relas = (struct upatch_relocation *)ctx->rela_shdr->sh_addr; + const char *strings = (const char *)ctx->string_shdr->sh_addr; -struct process_entity *get_process(struct target_entity *target) -{ - struct process_entity *process = NULL; + size_t i; + struct upatch_function *func; + const char *func_name; - mutex_lock(&target->process_lock); - process = do_get_process_and_free_exit_process(target); - if (!process) { - process = new_process(target); - if (!process) { - log_err("cannot alloc process tgid %d, for target %s\n", - task_tgid_nr(current), target->path); - goto out; + struct patch_info *info; + struct pc_pair *entry; + + info = kzalloc(sizeof(struct patch_info), GFP_KERNEL); + if (!info) { + log_err("failed to alloc patch info\n"); + return -ENOMEM; + } + + hash_init(info->pc_maps); + for (i = 0; i < func_num; ++i) { + func = &funcs[i]; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + free_patch_info(info); + return -ENOMEM; } + + func_name = strings + relas[i].name.r_addend; + entry->old_pc = funcs[i].old_addr + ctx->load_bias + ctx->target->load_offset; + entry->new_pc = funcs[i].new_addr; + hash_add(info->pc_maps, &entry->node, entry->old_pc); + log_debug("function: 0x%08lx -> 0x%08lx, name: '%s'\n", entry->old_pc, entry->new_pc, func_name); } -out: - mutex_unlock(&target->process_lock); - return process; + list_add(&info->node, &process->loaded_patches); + info->patch = patch; + process->latest_patch = info; + + return 0; } diff --git a/upatch-manage/process_entity.h b/upatch-manage/process_entity.h index b2f767fa..e99940cd 100644 --- a/upatch-manage/process_entity.h +++ b/upatch-manage/process_entity.h @@ -30,6 +30,7 @@ struct pid; struct task_struct; +struct patch_context; struct patch_entity; struct target_entity; @@ -44,16 +45,15 @@ struct pc_pair { struct patch_info { struct patch_entity *patch; - struct list_head list; + struct list_head node; DECLARE_HASHTABLE(pc_maps, FUNC_HASH_BITS); }; // target may be loaded into different process // due to latency of uprobe handle, process may dealy patch loading struct process_entity { - struct pid* pid_s; - struct task_struct* task; - struct patch_info *active_info; + struct pid *pid; + struct task_struct *task; // multi-thread may trap and run uprobe_handle, we only need one to load patch struct mutex lock; @@ -62,15 +62,19 @@ struct process_entity { // so we have to maintain all patches the process loaded // For example we load and active p1, p2, p3, the patches list will be p3->p2->p1 // when we want to active p2, we just look through this list and active p2 to avoid load p2 again + struct patch_info *latest_patch; // latest actived patch struct list_head loaded_patches; // patch_info list head - - struct list_head list; + struct list_head process_node; // target process list node }; -struct process_entity *get_process(struct target_entity *target); +struct process_entity *new_process(struct target_entity *target); void free_process(struct process_entity *process); -void free_patch_info(struct patch_info *info); +struct process_entity *get_process(struct target_entity *target); + +struct patch_info *process_find_loaded_patch(struct process_entity *process, struct patch_entity *patch); + +int process_write_patch_info(struct process_entity *process, struct patch_entity *patch, struct patch_context *ctx); #endif // _UPATCH_MANAGE_PROCESS_ENTITY_H diff --git a/upatch-manage/symbol_resolve.c b/upatch-manage/symbol_resolve.c index 5cea7457..456bb2fc 100644 --- a/upatch-manage/symbol_resolve.c +++ b/upatch-manage/symbol_resolve.c @@ -576,7 +576,7 @@ static bool is_vma_other_so_text(struct patch_context *ctx, struct vm_area_struc return false; } - if (strcmp(file_path, ctx->target->file_name) == 0) { + if (strcmp(file_path, ctx->target->path) == 0) { return false; } diff --git a/upatch-manage/target_entity.c b/upatch-manage/target_entity.c index 0f7d56e7..d3fa1f75 100644 --- a/upatch-manage/target_entity.c +++ b/upatch-manage/target_entity.c @@ -25,10 +25,8 @@ #include "patch_entity.h" #include "process_entity.h" -#include "patch_manage.h" #include "util.h" -#define TARGET_TABLE_HASH_BITS 4 #define ELF_ADDR_MAX UINT_MAX static const char *SHSTRTAB_NAME = ".shstrtab"; @@ -39,38 +37,6 @@ static const char *PLT_RELA_NAME = ".rela.plt"; static const char *PLT_NAME = ".plt"; static const char *GOT_NAME = ".got"; -DEFINE_HASHTABLE(g_target_table, TARGET_TABLE_HASH_BITS); -DEFINE_MUTEX(g_target_table_lock); - -static void clear_target_metadata(struct target_metadata *meta) -{ - KFREE_CLEAR(meta->file_name); - - VFREE_CLEAR(meta->ehdr); - VFREE_CLEAR(meta->phdrs); - VFREE_CLEAR(meta->shdrs); - - VFREE_CLEAR(meta->symtab); - VFREE_CLEAR(meta->dynsym); - VFREE_CLEAR(meta->dynamic); - VFREE_CLEAR(meta->rela_dyn); - VFREE_CLEAR(meta->rela_plt); - - VFREE_CLEAR(meta->shstrtab); - VFREE_CLEAR(meta->strtab); - VFREE_CLEAR(meta->dynstr); - - meta->symtab_num = 0; - meta->dynamic_num = 0; - meta->dynsym_num = 0; - meta->rela_dyn_num = 0; - meta->rela_plt_num = 0; - - meta->shstrtab_len = 0; - meta->strtab_len = 0; - meta->dynstr_len = 0; -} - static int resolve_target_sections(struct target_metadata *meta, struct file *target) { Elf_Shdr *shdrs = meta->shdrs; @@ -270,52 +236,108 @@ static int resolve_target_address(struct target_metadata *meta) return 0; } -static int resolve_target_metadata(struct target_metadata *meta, struct file *target) +static void destroy_target_metadata(struct target_metadata *meta) +{ + KFREE_CLEAR(meta->path); + iput(meta->inode); + meta->inode = NULL; + + meta->size = 0; + + VFREE_CLEAR(meta->ehdr); + VFREE_CLEAR(meta->phdrs); + VFREE_CLEAR(meta->shdrs); + + VFREE_CLEAR(meta->symtab); + VFREE_CLEAR(meta->dynsym); + VFREE_CLEAR(meta->dynamic); + VFREE_CLEAR(meta->rela_dyn); + VFREE_CLEAR(meta->rela_plt); + + VFREE_CLEAR(meta->shstrtab); + VFREE_CLEAR(meta->strtab); + VFREE_CLEAR(meta->dynstr); + + meta->symtab_num = 0; + meta->dynamic_num = 0; + meta->dynsym_num = 0; + meta->rela_dyn_num = 0; + meta->rela_plt_num = 0; + + meta->shstrtab_len = 0; + meta->strtab_len = 0; + meta->dynstr_len = 0; + + meta->need_load_bias = false; + meta->vma_offset = 0; + meta->load_offset = 0; + + meta->tls_size = 0; + meta->tls_align = 0; + + meta->plt_addr = 0; + meta->got_addr = 0; + meta->plt_size = 0; + meta->got_size = 0; +} + +static int resolve_target_metadata(struct target_metadata *meta, const char *file_path) { - int ret = 0; + struct file *file; + int ret; - if (!target->f_path.dentry) { - log_err("invalid file dentry\n"); - ret = -EINVAL; + file = filp_open(file_path, O_RDONLY, 0); + if (IS_ERR(file)) { + log_err("failed to open '%s'\n", file_path); + ret = PTR_ERR(file); + file = NULL; goto out; } - meta->file_name = kstrdup(target->f_path.dentry->d_name.name, GFP_KERNEL); - if (!meta->file_name) { - log_err("failed to alloc filename\n"); + meta->path = kstrdup(file_path, GFP_KERNEL); + if (!meta->path) { + log_err("failed to alloc file path\n"); ret = -ENOMEM; goto out; } - meta->file_size = i_size_read(file_inode(target)); - meta->ehdr = vmalloc_read(target, 0, sizeof(Elf_Ehdr)); + meta->inode = igrab(file_inode(file)); + if (!meta->inode) { + log_err("file '%s' inode is invalid\n", meta->path); + ret = -ENOENT; + goto out; + } + + meta->size = i_size_read(meta->inode); + + meta->ehdr = vmalloc_read(file, 0, sizeof(Elf_Ehdr)); if (IS_ERR(meta->ehdr)) { - ret = PTR_ERR(meta->ehdr); log_err("failed to read elf header\n"); + ret = PTR_ERR(meta->ehdr); goto out; } - if (!is_valid_target(meta->ehdr, meta->file_size)) { - ret = -ENOEXEC; + if (!is_valid_target(meta->ehdr, meta->size)) { log_err("invalid file format\n"); + ret = -ENOEXEC; goto out; } - meta->phdrs = vmalloc_read(target, meta->ehdr->e_phoff, meta->ehdr->e_phentsize * meta->ehdr->e_phnum); + meta->phdrs = vmalloc_read(file, meta->ehdr->e_phoff, meta->ehdr->e_phentsize * meta->ehdr->e_phnum); if (IS_ERR(meta->phdrs)) { - ret = PTR_ERR(meta->phdrs); log_err("failed to read program header\n"); + ret = PTR_ERR(meta->phdrs); goto out; } - meta->shdrs = vmalloc_read(target, meta->ehdr->e_shoff, meta->ehdr->e_shentsize * meta->ehdr->e_shnum); + meta->shdrs = vmalloc_read(file, meta->ehdr->e_shoff, meta->ehdr->e_shentsize * meta->ehdr->e_shnum); if (IS_ERR(meta->shdrs)) { - ret = PTR_ERR(meta->shdrs); log_err("failed to read section header\n"); + ret = PTR_ERR(meta->shdrs); goto out; } - ret = resolve_target_sections(meta, target); + ret = resolve_target_sections(meta, file); if (ret) { log_err("failed to resolve target sections\n"); goto out; @@ -327,108 +349,100 @@ static int resolve_target_metadata(struct target_metadata *meta, struct file *ta goto out; } - return 0; - out: - clear_target_metadata(meta); + if (file) { + filp_close(file, NULL); + } + if (ret) { + destroy_target_metadata(meta); + } return ret; } static int resolve_target_entity(struct target_entity *target, const char *file_path) { - int ret = 0; - struct file *file = NULL; - - init_rwsem(&target->patch_lock); - mutex_init(&target->process_lock); - INIT_HLIST_NODE(&target->node); - INIT_LIST_HEAD(&target->offset_node); - INIT_LIST_HEAD(&target->all_patch_list); - INIT_LIST_HEAD(&target->actived_patch_list); - INIT_LIST_HEAD(&target->process_head); - - file = filp_open(file_path, O_RDONLY, 0); // open file by inode - if (IS_ERR(file)) { - log_err("failed to open '%s'\n", file_path); - return PTR_ERR(file); - } + int ret; - target->inode = igrab(file_inode(file)); - if (!target->inode) { - log_err("file '%s' inode is invalid\n", file_path); - ret = -ENOENT; - goto out; + ret = resolve_target_metadata(&target->meta, file_path); + if (ret) { + return ret; } - target->path = kstrdup(file_path, GFP_KERNEL); - if (!target->path) { - iput(target->inode); - ret = -ENOMEM; - log_err("faild to alloc filename\n"); - goto out; - } + INIT_HLIST_NODE(&target->table_node); - ret = resolve_target_metadata(&target->meta, file); - if (ret != 0) { - iput(target->inode); - KFREE_CLEAR(target->path); - goto out; - } + init_rwsem(&target->action_rwsem); + INIT_LIST_HEAD(&target->loaded_list); + INIT_LIST_HEAD(&target->actived_list); + INIT_LIST_HEAD(&target->func_list); -out: - filp_close(file, NULL); - return ret; + mutex_init(&target->process_mutex); + INIT_LIST_HEAD(&target->process_list); + + return 0; } -struct target_entity *get_target_entity_by_inode(struct inode *inode) +static void destroy_target_entity(struct target_entity *target) { - struct target_entity *target; - struct target_entity *found = NULL; + struct process_entity *process; + struct process_entity *tmp; - mutex_lock(&g_target_table_lock); - hash_for_each_possible(g_target_table, target, node, inode->i_ino) { - if (target->inode == inode) { - found = target; - break; - } + destroy_target_metadata(&target->meta); + + WARN_ON(!hlist_unhashed(&target->table_node)); + + WARN_ON(rwsem_is_locked(&target->action_rwsem)); + WARN_ON(!list_empty(&target->loaded_list)); + WARN_ON(!list_empty(&target->actived_list)); + WARN_ON(!list_empty(&target->func_list)); + INIT_LIST_HEAD(&target->loaded_list); + INIT_LIST_HEAD(&target->actived_list); + INIT_LIST_HEAD(&target->func_list); + + mutex_destroy(&target->process_mutex); + list_for_each_entry_safe(process, tmp, &target->process_list, process_node) { + list_del_init(&process->process_node); + free_process(process); } - mutex_unlock(&g_target_table_lock); - return found; + INIT_LIST_HEAD(&target->process_list); } -/* public interface */ -struct target_entity *get_target_entity(const char *path) +static inline struct target_function *target_get_function(struct target_entity *target, u64 addr) { - struct target_entity *target; - struct inode *inode; + struct target_function *target_func; - inode = get_path_inode(path); - if (!inode) { - log_err("failed to get '%s' inode\n", path); - return NULL; + list_for_each_entry(target_func, &target->func_list, func_node) { + if (target_func->addr == addr) { + return target_func; + } } - target = get_target_entity_by_inode(inode); - iput(inode); - - return target; + return NULL; } -static void insert_target(struct target_entity *target) +static inline struct process_entity *target_get_process(struct target_entity *target) { - mutex_lock(&g_target_table_lock); - hash_add(g_target_table, &target->node, target->inode->i_ino); - mutex_unlock(&g_target_table_lock); + struct pid *current_pid = get_task_pid(current, PIDTYPE_TGID); + struct process_entity *process; + struct process_entity *found = NULL; + + list_for_each_entry(process, &target->process_list, process_node) { + if (pid_nr(process->pid) == pid_nr(current_pid)) { + found = process; + break; + } + } + + put_pid(current_pid); + return found; } +/* public interface */ struct target_entity *new_target_entity(const char *file_path) { struct target_entity *target = NULL; - int ret = 0; - - log_debug("create patch target entity '%s'\n", file_path); + int ret; - if (!file_path) { + if (unlikely(!file_path)) { return ERR_PTR(-EINVAL); } @@ -439,69 +453,114 @@ struct target_entity *new_target_entity(const char *file_path) } ret = resolve_target_entity(target, file_path); - if (ret != 0) { + if (ret) { kfree(target); return ERR_PTR(ret); } - insert_target(target); return target; } -// caller should lock g_target_table_lock void free_target_entity(struct target_entity *target) { - struct process_entity *process; - struct process_entity *tmp_pro; - struct patch_entity *patch; - struct patched_offset *off; + if (unlikely(!target)) { + return; + } - log_debug("free patch target '%s'\n", target->path); - down_write(&target->patch_lock); + log_debug("free patch target '%s'\n", target->meta.path); + destroy_target_entity(target); + kfree(target); +} - list_for_each_entry(off, &target->offset_node, list) { - log_err("found uprobe in 0x%lx\n", (unsigned long)off->offset); - } +int target_add_function(struct target_entity *target, struct upatch_function *func, bool *need_register) +{ + struct target_function *target_func; - list_for_each_entry(patch, &target->actived_patch_list, actived_node) { - log_err("found actived patch '%s'\n", patch->path ? patch->path : "NULL"); + if (!target || !func || !need_register) { + return -EINVAL; } - list_for_each_entry(patch, &target->all_patch_list, patch_node) { - log_err("found patch '%s'\n", patch->path ? patch->path : "NULL"); - } + *need_register = false; - mutex_lock(&target->process_lock); - list_for_each_entry_safe(process, tmp_pro, &target->process_head, list) { - free_process(process); + // get or alloc target function + target_func = target_get_function(target, func->old_addr); + if (!target_func) { + target_func = kzalloc(sizeof(*target_func), GFP_KERNEL); + if (!target_func) { + log_err("failed to alloc target function\n"); + return -ENOMEM; + } + target_func->addr = func->old_addr; + target_func->count = 0; + INIT_LIST_HEAD(&target_func->func_node); + + *need_register = true; + list_add(&target_func->func_node, &target->func_list); } - mutex_unlock(&target->process_lock); - iput(target->inode); - KFREE_CLEAR(target->path); - clear_target_metadata(&target->meta); - hash_del(&target->node); + target_func->count += 1; - target_unregister_uprobes(target); + return 0; +} - up_write(&target->patch_lock); +void target_remove_function(struct target_entity *target, struct upatch_function *func, bool *need_unregister) +{ + struct target_function *target_func; - kfree(target); + if (!target || !func || !need_unregister) { + return; + } + + *need_unregister = false; + + target_func = target_get_function(target, func->old_addr); + if (!target_func) { + log_warn("target does not have function\n"); + return; + } + + target_func->count -= 1; + if (!target_func->count) { + list_del_init(&target_func->func_node); + kfree(target_func); + *need_unregister = true; + } } -bool is_target_has_patch(const struct target_entity *target) +void target_gather_exited_processes(struct target_entity *target, struct list_head *process_list) { - return !list_empty(&target->all_patch_list); + struct process_entity *process; + struct process_entity *tmp; + + if (unlikely(!target || !process_list)) { + return; + } + + list_for_each_entry_safe(process, tmp, &target->process_list, process_node) { + if (!pid_task(process->pid, PIDTYPE_TGID)) { + list_move(&process->process_node, process_list); + } + } } -void __exit report_target_table_populated(void) +struct process_entity *target_get_or_create_process(struct target_entity *target) { - struct target_entity *target; - int bkt; + struct process_entity *process = NULL; - mutex_lock(&g_target_table_lock); - hash_for_each(g_target_table, bkt, target, node) { - log_err("found target '%s' on exit", target->path ? target->path : "(null)"); + if (unlikely(!target)) { + return NULL; } - mutex_unlock(&g_target_table_lock); + + process = target_get_process(target); + if (!process) { + log_debug("create process %d for '%s'\n", task_pid_nr(current), target->meta.path); + process = new_process(target); + if (IS_ERR(process)) { + log_err("failed to create target process, ret=%d\n", (int)PTR_ERR(process)); + return NULL; + } + list_add(&process->process_node, &target->process_list); + } + + return process; } diff --git a/upatch-manage/target_entity.h b/upatch-manage/target_entity.h index b5f0eb19..4ac52185 100644 --- a/upatch-manage/target_entity.h +++ b/upatch-manage/target_entity.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -44,12 +45,16 @@ #define GOT_ENTRY_SIZE sizeof(uintptr_t) // GOT entry size is pointer size struct inode; + +struct patch_entity; struct upatch_function; /* target elf metadata */ struct target_metadata { - const char *file_name; - loff_t file_size; + const char *path; + struct inode *inode; + + loff_t size; Elf_Ehdr *ehdr; Elf_Phdr *phdrs; @@ -88,62 +93,26 @@ struct target_metadata { size_t got_size; }; -struct target_entity { - const char *path; // patch file path - struct inode *inode; // target file inode - - struct target_metadata meta; // target file elf data - - /* - * there is only one thread to call active / deactive - * we don't need a lock - */ - struct list_head offset_node; // list of file offset of active patch function for struct patched_offset - struct hlist_node node; // all target store in hash table - - /* - * all patches related to this target, including active and deactive patches - * don't need lock. only load_patch, remove_patch, rmmod upatch_manage will read/write this list - * uprobe_handle will not use this list, and we limit there is only one thread to manage patch - */ - struct list_head all_patch_list; - - /* - * active patch list need lock - * uprobe handle will read it, active method will write it - */ - struct rw_semaphore patch_lock; - struct list_head actived_patch_list; - - /* - * target ELF may run in different process, such as a dynamic object - * every process will have a actived patch - */ - struct mutex process_lock; // uprobe handle will call free_process, so we need lock - struct list_head process_head; +/* target function record */ +struct target_function { + u64 addr; // target function address + size_t count; // target function patch count + struct list_head func_node; // target function list node }; -/* Patched address */ -struct patched_offset { - loff_t offset; // offset of the patched func addr - struct list_head funcs_head; // patched function list head - struct list_head list; // address list node -}; +struct target_entity { + struct target_metadata meta; // target file metadata + struct hlist_node table_node; // global target hash table node -/* Patched function record */ -struct patched_func_node { - struct upatch_function *func; // patched function - struct list_head list; -}; + struct rw_semaphore action_rwsem; // target action rw semaphore -/* - * Find a target entity - * @param ino: target file inode number - * @return target entity - */ -struct target_entity *get_target_entity(const char* target_path); + struct list_head loaded_list; // target loaded patches + struct list_head actived_list; // target actived patches + struct list_head func_list; // target registered functions -struct target_entity *get_target_entity_by_inode(struct inode *inode); + struct mutex process_mutex; + struct list_head process_list; // all processes of the target +}; /* * Load a target entity @@ -153,20 +122,43 @@ struct target_entity *get_target_entity_by_inode(struct inode *inode); struct target_entity *new_target_entity(const char *file_path); /* - * Remove a target entity + * Free a target entity * @param target: target entity * @return void */ void free_target_entity(struct target_entity *target); /* - * Check if a target has related patches. DEACTIVE/ACTIVE patches are all counted + * Add a patch function to target entity * @param target: target entity - * @param offset: target offset + * @param func: patch function + * @param need_register: target offset needs register uprobe * @return result */ -bool is_target_has_patch(const struct target_entity *target); +int target_add_function(struct target_entity *target, struct upatch_function *func, bool *need_register); -void __exit report_target_table_populated(void); +/* + * Remove a patch function from target entity + * @param target: target entity + * @param func: patch function + * @param need_unregister: target offset needs unregister uprobe + * @return result + */ +void target_remove_function(struct target_entity *target, struct upatch_function *func, bool *need_unregister); + +/* + * Collect all exited process into a list + * @param target: target entity + * @param process_list: exited process list + * @return void + */ +void target_gather_exited_processes(struct target_entity *target, struct list_head *process_list); + +/* + * Get or create a process entity from target entity + * @param target: target entity + * @return process entity + */ +struct process_entity *target_get_or_create_process(struct target_entity *target); #endif // _UPATCH_MANAGE_TARGET_ENTITY_H diff --git a/upatch-manage/util.h b/upatch-manage/util.h index 62497ce1..c5e08182 100644 --- a/upatch-manage/util.h +++ b/upatch-manage/util.h @@ -158,7 +158,7 @@ static inline bool is_valid_str(const char *strtab, size_t strtab_len, size_t of const char *start; size_t remain_len; - if (unlikely(offset == 0 || offset >= strtab_len - 1)) { + if (unlikely(offset >= strtab_len - 1)) { return false; } @@ -181,11 +181,10 @@ static inline struct inode *get_path_inode(const char *file) return NULL; } - inode = path.dentry->d_inode; - path_put(&path); + inode = igrab(path.dentry->d_inode); // will increase inode refcnt, need call iput after use - // will increase inode refcnt, need call iput after use - return igrab(inode); + path_put(&path); + return inode; } #endif // _UPATCH_MANAGE_UTIL_H -- Gitee