/*- * Copyright (c) 2014, 2015 Mark Johnston * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #ifdef __FBSDID __FBSDID("$FreeBSD: head/lib/libproc/tests/proc_test.c 286863 2015-08-17 23:19:36Z emaste $"); #endif __RCSID("$NetBSD: proc_test.c,v 1.6 2018/07/20 20:50:34 christos Exp $"); #include #include #include #include #include #include #include #include #include #include static const char *aout_object = "a.out"; #if defined(__NetBSD__) static const char *r_debug_state = "_rtld_debug_state"; #elif defined(__FreeBSD__) static const char *r_debug_state = "r_debug_state"; #endif #if defined(__NetBSD__) static const char *ldelf_object = "ld.elf_so"; #elif defined(__FreeBSD__) static const char *ldelf_object = "ld-elf.so.1"; #endif static const char *target_prog_file = "target_prog"; #ifdef __NetBSD__ static char * basename_r(const char *path, char *buf) { // XX: We know this works strlcpy(buf, strrchr(path, '/') + 1, PATH_MAX); return buf; } #endif /* * Run the test program. If the sig parameter is set to true, the test program * will deliver SIGUSR1 to itself during execution. */ static struct proc_handle * start_prog(const struct atf_tc *tc, bool sig) { char *argv[3]; struct proc_handle *phdl; int error; asprintf(&argv[0], "%s/%s", atf_tc_get_config_var(tc, "srcdir"), target_prog_file); ATF_REQUIRE(argv[0] != NULL); if (sig) { argv[1] = strdup("-s"); argv[2] = NULL; } else { argv[1] = NULL; } error = proc_create(argv[0], argv, NULL, NULL, &phdl); ATF_REQUIRE_EQ_MSG(error, 0, "failed to run '%s'", target_prog_file); ATF_REQUIRE(phdl != NULL); free(argv[0]); free(argv[1]); return (phdl); } static void set_bkpt(struct proc_handle *phdl, uintptr_t addr, proc_breakpoint_t *saved) { int error; error = proc_bkptset(phdl, addr, saved); ATF_REQUIRE_EQ_MSG(error, 0, "failed to set breakpoint at 0x%jx", (uintmax_t)addr); } static void remove_bkpt(struct proc_handle *phdl, uintptr_t addr, proc_breakpoint_t *val) { int error; error = proc_bkptdel(phdl, addr, val); ATF_REQUIRE_EQ_MSG(error, 0, "failed to delete breakpoint at 0x%jx", (uintmax_t)addr); error = proc_regset(phdl, REG_PC, addr); ATF_REQUIRE_EQ_MSG(error, 0, "failed to reset program counter"); } /* * Wait for the specified process to hit a breakpoint at the specified symbol. */ static void verify_bkpt(struct proc_handle *phdl, GElf_Sym *sym, const char *symname, const char *mapname) { char mapbname[MAXPATHLEN], *name; GElf_Sym tsym; prmap_t *map; size_t namesz; u_long addr; int error, state; state = proc_wstatus(phdl); ATF_REQUIRE_EQ_MSG(state, PS_STOP, "process has state %d", state); /* Get the program counter and decrement it. */ error = proc_regget(phdl, REG_PC, &addr); ATF_REQUIRE_EQ_MSG(error, 0, "failed to obtain PC for '%s'", target_prog_file); proc_bkptregadj(&addr); /* * Make sure the PC matches the expected value obtained from the symbol * definition we looked up earlier. */ ATF_CHECK_EQ_MSG(addr, sym->st_value, "program counter 0x%lx doesn't match expected value 0x%jx", addr, (uintmax_t)sym->st_value); /* * Ensure we can look up the r_debug_state symbol using its starting * address and that the resulting symbol matches the one we found using * a name lookup. */ namesz = strlen(symname) + 1; name = malloc(namesz); ATF_REQUIRE(name != NULL); error = proc_addr2sym(phdl, addr, name, namesz, &tsym); ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up symbol at 0x%lx", addr); ATF_REQUIRE_EQ(memcmp(sym, &tsym, sizeof(*sym)), 0); ATF_REQUIRE_EQ_MSG(strcmp(symname, name), 0, "expected symbol name '%s' doesn't match '%s'", symname, name); free(name); map = proc_addr2map(phdl, addr); ATF_REQUIRE_MSG(map != NULL, "failed to look up map for address 0x%lx", addr); basename_r(map->pr_mapname, mapbname); ATF_REQUIRE_EQ_MSG(strcmp(mapname, mapbname), 0, "expected map name '%s' doesn't match '%s'", mapname, mapbname); } ATF_TC(map_alias_obj2map); ATF_TC_HEAD(map_alias_obj2map, tc) { atf_tc_set_md_var(tc, "descr", "Callers are supposed to be able to use \"a.out\" as an alias for " "the program executable. Make sure that proc_obj2map() handles " "this properly."); } ATF_TC_BODY(map_alias_obj2map, tc) { struct proc_handle *phdl; prmap_t *map1, *map2; phdl = start_prog(tc, false); /* Initialize the rtld_db handle. */ (void)proc_rdagent(phdl); /* Ensure that "target_prog" and "a.out" return the same map. */ map1 = proc_obj2map(phdl, target_prog_file); ATF_REQUIRE_MSG(map1 != NULL, "failed to look up map for '%s'", target_prog_file); map2 = proc_obj2map(phdl, aout_object); ATF_REQUIRE_MSG(map2 != NULL, "failed to look up map for '%s'", aout_object); ATF_CHECK_EQ(strcmp(map1->pr_mapname, map2->pr_mapname), 0); ATF_CHECK_EQ_MSG(proc_detach(phdl, PRELEASE_HANG), 0, "failed to detach"); proc_free(phdl); } ATF_TC(map_alias_name2map); ATF_TC_HEAD(map_alias_name2map, tc) { atf_tc_set_md_var(tc, "descr", "Callers are supposed to be able to use \"a.out\" as an alias for " "the program executable. Make sure that proc_name2map() handles " "this properly."); } ATF_TC_BODY(map_alias_name2map, tc) { struct proc_handle *phdl; prmap_t *map1, *map2; phdl = start_prog(tc, false); /* Initialize the rtld_db handle. */ (void)proc_rdagent(phdl); /* Ensure that "target_prog" and "a.out" return the same map. */ map1 = proc_name2map(phdl, target_prog_file); ATF_REQUIRE_MSG(map1 != NULL, "failed to look up map for '%s'", target_prog_file); map2 = proc_name2map(phdl, aout_object); ATF_REQUIRE_MSG(map2 != NULL, "failed to look up map for '%s'", aout_object); ATF_CHECK_EQ(strcmp(map1->pr_mapname, map2->pr_mapname), 0); ATF_CHECK_EQ_MSG(proc_detach(phdl, PRELEASE_HANG), 0, "failed to detach"); proc_free(phdl); } ATF_TC(map_alias_name2sym); ATF_TC_HEAD(map_alias_name2sym, tc) { atf_tc_set_md_var(tc, "descr", "Callers are supposed to be able to use \"a.out\" as an alias for " "the program executable. Make sure that proc_name2sym() handles " "this properly."); } ATF_TC_BODY(map_alias_name2sym, tc) { GElf_Sym sym1, sym2; prsyminfo_t si1, si2; struct proc_handle *phdl; int error; phdl = start_prog(tc, false); /* Initialize the rtld_db handle. */ (void)proc_rdagent(phdl); /* * Make sure that "target_prog:main" and "a.out:main" return the same * symbol. */ error = proc_name2sym(phdl, target_prog_file, "main", &sym1, &si1); ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'main' via %s", target_prog_file); error = proc_name2sym(phdl, aout_object, "main", &sym2, &si2); ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'main' via %s", aout_object); ATF_CHECK_EQ(memcmp(&sym1, &sym2, sizeof(sym1)), 0); ATF_CHECK_EQ(si1.prs_id, si2.prs_id); ATF_CHECK_EQ_MSG(proc_detach(phdl, PRELEASE_HANG), 0, "failed to detach"); proc_free(phdl); } ATF_TC(symbol_lookup); ATF_TC_HEAD(symbol_lookup, tc) { atf_tc_set_md_var(tc, "descr", "Look up a couple of well-known symbols in the test program, place " "breakpoints on them, and verify that we hit the breakpoints. Also " "make sure that we can use the breakpoint address to look up the " "corresponding symbol."); } ATF_TC_BODY(symbol_lookup, tc) { GElf_Sym main_sym, r_debug_state_sym; struct proc_handle *phdl; proc_breakpoint_t saved; int error; phdl = start_prog(tc, false); error = proc_name2sym(phdl, target_prog_file, "main", &main_sym, NULL); ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'main'"); error = proc_name2sym(phdl, ldelf_object, r_debug_state, &r_debug_state_sym, NULL); ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up '%s'", r_debug_state); set_bkpt(phdl, (uintptr_t)r_debug_state_sym.st_value, &saved); ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); verify_bkpt(phdl, &r_debug_state_sym, r_debug_state, ldelf_object); remove_bkpt(phdl, (uintptr_t)r_debug_state_sym.st_value, &saved); set_bkpt(phdl, (uintptr_t)main_sym.st_value, &saved); ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); verify_bkpt(phdl, &main_sym, "main", target_prog_file); remove_bkpt(phdl, (uintptr_t)main_sym.st_value, &saved); ATF_CHECK_EQ_MSG(proc_detach(phdl, PRELEASE_HANG), 0, "failed to detach"); proc_free(phdl); } ATF_TC(symbol_lookup_fail); ATF_TC_HEAD(symbol_lookup_fail, tc) { atf_tc_set_md_var(tc, "descr", "Verify that proc_addr2sym() returns an error when given an offset " "that it cannot resolve."); } ATF_TC_BODY(symbol_lookup_fail, tc) { char symname[32]; GElf_Sym sym; struct proc_handle *phdl; prmap_t *map; int error; phdl = start_prog(tc, false); /* Initialize the rtld_db handle. */ (void)proc_rdagent(phdl); map = proc_obj2map(phdl, target_prog_file); ATF_REQUIRE_MSG(map != NULL, "failed to look up map for '%s'", target_prog_file); /* * We shouldn't be able to find symbols at the beginning of a mapped * file. */ error = proc_addr2sym(phdl, map->pr_vaddr, symname, sizeof(symname), &sym); ATF_REQUIRE_MSG(error != 0, "unexpectedly found a symbol"); ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); proc_free(phdl); } ATF_TC(signal_forward); ATF_TC_HEAD(signal_forward, tc) { atf_tc_set_md_var(tc, "descr", "Run the test program in a mode which causes it to send a signal " "to itself. Make sure that we intercept the signal and that " "proc_continue() forwards it to the process."); } ATF_TC_BODY(signal_forward, tc) { struct proc_handle *phdl; int state, status; phdl = start_prog(tc, true); ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); /* The process should have been interrupted by a signal. */ state = proc_wstatus(phdl); ATF_REQUIRE_EQ_MSG(state, PS_STOP, "process has unexpected state %d", state); /* Continue execution and allow the signal to be delivered. */ ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); /* * Make sure the process exited with status 0. If it didn't receive the * SIGUSR1 that it sent to itself, it'll exit with a non-zero exit * status, causing the test to fail. */ state = proc_wstatus(phdl); ATF_REQUIRE_EQ_MSG(state, PS_UNDEAD, "process has unexpected state %d", state); status = proc_getwstat(phdl); ATF_REQUIRE(status >= 0); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); proc_free(phdl); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, map_alias_obj2map); ATF_TP_ADD_TC(tp, map_alias_name2map); ATF_TP_ADD_TC(tp, map_alias_name2sym); ATF_TP_ADD_TC(tp, symbol_lookup); ATF_TP_ADD_TC(tp, symbol_lookup_fail); ATF_TP_ADD_TC(tp, signal_forward); return (atf_no_error()); }