• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016 GitHub, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <fcntl.h>
17 #include <dlfcn.h>
18 #include <stdint.h>
19 #include <string.h>
20 #include <link.h>
21 #include <sys/mman.h>
22 #include <sys/mount.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <unistd.h>
27 
28 #include "bcc_elf.h"
29 #include "bcc_perf_map.h"
30 #include "bcc_proc.h"
31 #include "bcc_syms.h"
32 #include "common.h"
33 #include "vendor/tinyformat.hpp"
34 
35 #include "catch.hpp"
36 
37 using namespace std;
38 
39 static pid_t spawn_child(void *, bool, bool, int (*)(void *));
40 
41 TEST_CASE("language detection", "[c_api]") {
42   const char *c = bcc_procutils_language(getpid());
43   REQUIRE(c);
44   REQUIRE(string(c).compare("c") == 0);
45 }
46 
47 TEST_CASE("shared object resolution", "[c_api]") {
48   char *libm = bcc_procutils_which_so("m", 0);
49   REQUIRE(libm);
50   REQUIRE(libm[0] == '/');
51   REQUIRE(string(libm).find("libm.so") != string::npos);
52   free(libm);
53 }
54 
55 TEST_CASE("shared object resolution using loaded libraries", "[c_api]") {
56   char *libelf = bcc_procutils_which_so("elf", getpid());
57   REQUIRE(libelf);
58   REQUIRE(libelf[0] == '/');
59   REQUIRE(string(libelf).find("libelf") != string::npos);
60   free(libelf);
61 }
62 
63 TEST_CASE("binary resolution with `which`", "[c_api]") {
64   char *ld = bcc_procutils_which("ld");
65   REQUIRE(ld);
66   REQUIRE(ld[0] == '/');
67   free(ld);
68 }
69 
_test_ksym(const char * sym,uint64_t addr,void * _)70 static void _test_ksym(const char *sym, uint64_t addr, void *_) {
71   if (!strcmp(sym, "startup_64"))
72     REQUIRE(addr != 0x0ull);
73 }
74 
75 TEST_CASE("list all kernel symbols", "[c_api]") {
76   if (geteuid() != 0)
77     return;
78   bcc_procutils_each_ksym(_test_ksym, NULL);
79 }
80 
81 TEST_CASE("file-backed mapping identification") {
82   CHECK(bcc_mapping_is_file_backed("/bin/ls") == 1);
83   CHECK(bcc_mapping_is_file_backed("") == 0);
84   CHECK(bcc_mapping_is_file_backed("//anon") == 0);
85   CHECK(bcc_mapping_is_file_backed("/dev/zero") == 0);
86   CHECK(bcc_mapping_is_file_backed("/anon_hugepage") == 0);
87   CHECK(bcc_mapping_is_file_backed("/anon_hugepage (deleted)") == 0);
88   CHECK(bcc_mapping_is_file_backed("[stack") == 0);
89   CHECK(bcc_mapping_is_file_backed("/SYSV") == 0);
90   CHECK(bcc_mapping_is_file_backed("[heap]") == 0);
91 }
92 
93 TEST_CASE("resolve symbol name in external library", "[c_api]") {
94   struct bcc_symbol sym;
95 
96   REQUIRE(bcc_resolve_symname("c", "malloc", 0x0, 0, nullptr, &sym) == 0);
97   REQUIRE(string(sym.module).find("libc.so") != string::npos);
98   REQUIRE(sym.module[0] == '/');
99   REQUIRE(sym.offset != 0);
100   bcc_procutils_free(sym.module);
101 }
102 
103 TEST_CASE("resolve symbol name in external library using loaded libraries", "[c_api]") {
104   struct bcc_symbol sym;
105 
106   REQUIRE(bcc_resolve_symname("bcc", "bcc_procutils_which", 0x0, getpid(), nullptr, &sym) == 0);
107   REQUIRE(string(sym.module).find("libbcc.so") != string::npos);
108   REQUIRE(sym.module[0] == '/');
109   REQUIRE(sym.offset != 0);
110   bcc_procutils_free(sym.module);
111 }
112 
_a_test_function(const char * a_string)113 extern "C" int _a_test_function(const char *a_string) {
114   int i;
115   for (i = 0; a_string[i]; ++i)
116     ;
117   return i;
118 }
119 
setup_tmp_mnts(void)120 static int setup_tmp_mnts(void) {
121   // Disconnect this mount namespace from its parent
122   if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) {
123     fprintf(stderr, "unable to mark / PRIVATE: %s\n", strerror(errno));
124     return -1;
125   }
126   // create a new tmpfs mounted on /tmp
127   if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL) < 0) {
128     fprintf(stderr, "unable to mount /tmp in mntns: %s\n", strerror(errno));
129     return -1;
130   }
131 
132   return 0;
133 }
134 
mntns_func(void * arg)135 static int mntns_func(void *arg) {
136   int in_fd, out_fd;
137   char buf[4096];
138   char libpath[1024];
139   ssize_t rb;
140   void *dlhdl;
141   struct link_map *lm;
142 
143   if (setup_tmp_mnts() < 0) {
144     return -1;
145   }
146 
147   // Find libz.so.1, if it's installed
148   dlhdl = dlopen("libz.so.1", RTLD_LAZY);
149   if (dlhdl == NULL) {
150     fprintf(stderr, "Unable to dlopen libz.so.1: %s\n", dlerror());
151     return -1;
152   }
153 
154   if (dlinfo(dlhdl, RTLD_DI_LINKMAP, &lm) < 0) {
155     fprintf(stderr, "Unable to find origin of libz.so.1: %s\n", dlerror());
156     return -1;
157   }
158 
159   strncpy(libpath, lm->l_name, 1024);
160   dlclose(dlhdl);
161   dlhdl = NULL;
162 
163   // Copy a shared library from shared mntns to private /tmp
164   snprintf(buf, 4096, "%s", libpath);
165   in_fd = open(buf, O_RDONLY);
166   if (in_fd < 0) {
167     fprintf(stderr, "Unable to open %s: %s\n", buf, strerror(errno));
168     return -1;
169   }
170 
171   out_fd = open("/tmp/libz.so.1", O_RDWR|O_CREAT|O_EXCL,
172       S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
173   if (out_fd < 0) {
174     fprintf(stderr, "Unable to open /tmp/libz.so.1: %s\n", strerror(errno));
175     return -1;
176   }
177   memset(buf, 0, sizeof (buf));
178   while ((rb = read(in_fd, buf, sizeof (buf))) > 0) {
179     if (write(out_fd, buf, rb) < 0) {
180       fprintf(stderr, "Write error: %s\n", strerror(errno));
181       return -1;
182     }
183   }
184   close(in_fd);
185   close(out_fd);
186 
187   dlhdl = dlopen("/tmp/libz.so.1", RTLD_NOW);
188   if (dlhdl == NULL) {
189     fprintf(stderr, "dlopen error: %s\n", dlerror());
190     return -1;
191   }
192 
193   sleep(5);
194   dlclose(dlhdl);
195 
196   return 0;
197 }
198 
199 extern int cmd_scanf(const char *cmd, const char *fmt, ...);
200 
201 TEST_CASE("resolve symbol addresses for a given PID", "[c_api]") {
202   struct bcc_symbol sym;
203   void *resolver = bcc_symcache_new(getpid(), nullptr);
204 
205   REQUIRE(resolver);
206 
207   SECTION("resolve in our own binary memory space") {
208     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
209             0);
210 
211     char *this_exe = realpath("/proc/self/exe", NULL);
212     REQUIRE(string(this_exe) == sym.module);
213     free(this_exe);
214 
215     REQUIRE(string("_a_test_function") == sym.name);
216   }
217 
218   SECTION("resolve in libbcc.so") {
219     void *libbcc = dlopen("libbcc.so", RTLD_LAZY | RTLD_NOLOAD);
220     REQUIRE(libbcc);
221 
222     void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
223     REQUIRE(libbcc_fptr);
224 
225     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0);
226     REQUIRE(string(sym.module).find("libbcc.so") != string::npos);
227     REQUIRE(string("bcc_resolve_symname") == sym.name);
228   }
229 
230   SECTION("resolve in libc") {
231     void *libc_fptr = dlsym(NULL, "strtok");
232     REQUIRE(libc_fptr);
233 
234     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0);
235     REQUIRE(sym.module);
236     REQUIRE(sym.module[0] == '/');
237     REQUIRE(string(sym.module).find("libc") != string::npos);
238 
239     // In some cases, a symbol may have multiple aliases. Since
240     // bcc_symcache_resolve() returns only the first alias of a
241     // symbol, this may not always be "strtok" even if it points
242     // to the same address.
243     bool sym_match = (string("strtok") == sym.name);
244     if (!sym_match) {
245       uint64_t exp_addr, sym_addr;
246       char cmd[256];
247       const char *cmdfmt = "nm %s | grep \" %s$\" | cut -f 1 -d \" \"";
248 
249       // Find address of symbol by the expected name
250       sprintf(cmd, cmdfmt, sym.module, "strtok");
251       REQUIRE(cmd_scanf(cmd, "%lx", &exp_addr) == 0);
252 
253       // Find address of symbol by the name that was
254       // returned by bcc_symcache_resolve()
255       sprintf(cmd, cmdfmt, sym.module, sym.name);
256       REQUIRE(cmd_scanf(cmd, "%lx", &sym_addr) == 0);
257 
258       // If both addresses match, they are definitely
259       // aliases of the same symbol
260       sym_match = (exp_addr == sym_addr);
261     }
262 
263     REQUIRE(sym_match);
264   }
265 
266   SECTION("resolve in separate mount namespace") {
267     pid_t child;
268     uint64_t addr = 0;
269 
270     child = spawn_child(0, true, true, mntns_func);
271     REQUIRE(child > 0);
272 
273     void *resolver = bcc_symcache_new(child, nullptr);
274     REQUIRE(resolver);
275 
276     REQUIRE(bcc_symcache_resolve_name(resolver, "/tmp/libz.so.1", "zlibVersion",
277         &addr) == 0);
278     REQUIRE(addr != 0);
279   }
280 }
281 
282 #define STACK_SIZE (1024 * 1024)
283 static char child_stack[STACK_SIZE];
284 
perf_map_path(pid_t pid)285 static string perf_map_path(pid_t pid) {
286   return tfm::format("/tmp/perf-%d.map", pid);
287 }
288 
make_perf_map_file(string & path,unsigned long long map_addr)289 static int make_perf_map_file(string &path, unsigned long long map_addr) {
290   FILE *file = fopen(path.c_str(), "w");
291   if (file == NULL) {
292     return -1;
293   }
294   fprintf(file, "%llx 10 dummy_fn\n", map_addr);
295   fprintf(file, "%llx 10 right_next_door_fn\n", map_addr + 0x10);
296   fclose(file);
297 
298   return 0;
299 }
300 
perf_map_func(void * arg)301 static int perf_map_func(void *arg) {
302   string path = perf_map_path(getpid());
303   if (make_perf_map_file(path, (unsigned long long)arg) < 0)
304     return -1;
305 
306   sleep(5);
307 
308   unlink(path.c_str());
309   return 0;
310 }
311 
perf_map_func_mntns(void * arg)312 static int perf_map_func_mntns(void *arg) {
313   string path = perf_map_path(getpid());
314 
315   if (setup_tmp_mnts() < 0) {
316     return -1;
317   }
318 
319   if (make_perf_map_file(path, (unsigned long long)arg) < 0)
320     return -1;
321 
322   sleep(5);
323 
324   unlink(path.c_str());
325   return 0;
326 }
327 
perf_map_func_noop(void * arg)328 static int perf_map_func_noop(void *arg) {
329   if (setup_tmp_mnts() < 0) {
330     return -1;
331   }
332 
333   sleep(5);
334 
335   return 0;
336 }
337 
spawn_child(void * map_addr,bool own_pidns,bool own_mntns,int (* child_func)(void *))338 static pid_t spawn_child(void *map_addr, bool own_pidns, bool own_mntns,
339     int (*child_func)(void *)) {
340   int flags = SIGCHLD;
341   if (own_pidns)
342     flags |= CLONE_NEWPID;
343   if (own_mntns)
344     flags |= CLONE_NEWNS;
345 
346   pid_t child = clone(child_func,
347       /* stack grows down */ child_stack + STACK_SIZE, flags, (void*)map_addr);
348   if (child < 0)
349     return -1;
350 
351   sleep(1); // let the child get set up
352   return child;
353 }
354 
355 TEST_CASE("resolve symbols using /tmp/perf-pid.map", "[c_api]") {
356   const int map_sz = 4096;
357   void *map_addr = mmap(NULL, map_sz, PROT_READ | PROT_EXEC,
358     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
359   REQUIRE(map_addr != MAP_FAILED);
360 
361   struct bcc_symbol sym;
362   pid_t child = -1;
363 
364   SECTION("same namespace") {
365     child = spawn_child(map_addr, /* own_pidns */ false, false, perf_map_func);
366     REQUIRE(child > 0);
367 
368     void *resolver = bcc_symcache_new(child, nullptr);
369     REQUIRE(resolver);
370 
371     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
372         &sym) == 0);
373     REQUIRE(sym.module);
374     REQUIRE(string(sym.module) == perf_map_path(child));
375     REQUIRE(string("dummy_fn") == sym.name);
376 
377     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr + 0x10,
378         &sym) == 0);
379     REQUIRE(sym.module);
380     REQUIRE(string(sym.module) == perf_map_path(child));
381     REQUIRE(string("right_next_door_fn") == sym.name);
382   }
383 
384   SECTION("separate namespace") {
385     child = spawn_child(map_addr, /* own_pidns */ true, false, perf_map_func);
386     REQUIRE(child > 0);
387 
388     void *resolver = bcc_symcache_new(child, nullptr);
389     REQUIRE(resolver);
390 
391     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
392         &sym) == 0);
393     REQUIRE(sym.module);
394     // child is PID 1 in its namespace
395     REQUIRE(string(sym.module) == perf_map_path(1));
396     REQUIRE(string("dummy_fn") == sym.name);
397     unlink("/tmp/perf-1.map");
398   }
399 
400   SECTION("separate pid and mount namespace") {
401     child = spawn_child(map_addr, /* own_pidns */ true, true,
402         perf_map_func_mntns);
403     REQUIRE(child > 0);
404 
405     void *resolver = bcc_symcache_new(child, nullptr);
406     REQUIRE(resolver);
407 
408     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
409         &sym) == 0);
410     REQUIRE(sym.module);
411     // child is PID 1 in its namespace
412     REQUIRE(string(sym.module) == perf_map_path(1));
413     REQUIRE(string("dummy_fn") == sym.name);
414   }
415 
416   SECTION("separate pid and mount namespace, perf-map in host") {
417     child = spawn_child(map_addr, /* own_pidns */ true, true,
418         perf_map_func_noop);
419     REQUIRE(child > 0);
420 
421     string path = perf_map_path(child);
422     REQUIRE(make_perf_map_file(path, (unsigned long long)map_addr) == 0);
423 
424     void *resolver = bcc_symcache_new(child, nullptr);
425     REQUIRE(resolver);
426 
427     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
428         &sym) == 0);
429     REQUIRE(sym.module);
430     // child is PID 1 in its namespace
431     REQUIRE(string(sym.module) == perf_map_path(child));
432     REQUIRE(string("dummy_fn") == sym.name);
433 
434     unlink(path.c_str());
435   }
436 
437 
438 
439   munmap(map_addr, map_sz);
440 }
441 
442 
443 TEST_CASE("get online CPUs", "[c_api]") {
444 	std::vector<int> cpus = ebpf::get_online_cpus();
445 	int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
446 	REQUIRE(cpus.size() == num_cpus);
447 }
448