diff options
| -rw-r--r-- | tools/perf/tests/builtin-test.c | 42 | ||||
| -rw-r--r-- | tools/perf/tests/dso-data.c | 214 | ||||
| -rw-r--r-- | tools/perf/tests/tests.h | 2 | ||||
| -rw-r--r-- | tools/perf/util/dso.c | 279 | ||||
| -rw-r--r-- | tools/perf/util/dso.h | 50 | ||||
| -rw-r--r-- | tools/perf/util/event.h | 5 | ||||
| -rw-r--r-- | tools/perf/util/evsel.c | 4 | ||||
| -rw-r--r-- | tools/perf/util/perf_regs.c | 10 | ||||
| -rw-r--r-- | tools/perf/util/perf_regs.h | 4 | ||||
| -rw-r--r-- | tools/perf/util/unwind-libunwind.c | 2 |
10 files changed, 574 insertions, 38 deletions
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 802e3cd50f6f..6f8b01bc6033 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | * | 3 | * |
| 4 | * Builtin regression testing command: ever growing number of sanity tests | 4 | * Builtin regression testing command: ever growing number of sanity tests |
| 5 | */ | 5 | */ |
| 6 | #include <unistd.h> | ||
| 7 | #include <string.h> | ||
| 6 | #include "builtin.h" | 8 | #include "builtin.h" |
| 7 | #include "intlist.h" | 9 | #include "intlist.h" |
| 8 | #include "tests.h" | 10 | #include "tests.h" |
| @@ -50,10 +52,18 @@ static struct test { | |||
| 50 | .func = test__pmu, | 52 | .func = test__pmu, |
| 51 | }, | 53 | }, |
| 52 | { | 54 | { |
| 53 | .desc = "Test dso data interface", | 55 | .desc = "Test dso data read", |
| 54 | .func = test__dso_data, | 56 | .func = test__dso_data, |
| 55 | }, | 57 | }, |
| 56 | { | 58 | { |
| 59 | .desc = "Test dso data cache", | ||
| 60 | .func = test__dso_data_cache, | ||
| 61 | }, | ||
| 62 | { | ||
| 63 | .desc = "Test dso data reopen", | ||
| 64 | .func = test__dso_data_reopen, | ||
| 65 | }, | ||
| 66 | { | ||
| 57 | .desc = "roundtrip evsel->name check", | 67 | .desc = "roundtrip evsel->name check", |
| 58 | .func = test__perf_evsel__roundtrip_name_test, | 68 | .func = test__perf_evsel__roundtrip_name_test, |
| 59 | }, | 69 | }, |
| @@ -172,6 +182,34 @@ static bool perf_test__matches(int curr, int argc, const char *argv[]) | |||
| 172 | return false; | 182 | return false; |
| 173 | } | 183 | } |
| 174 | 184 | ||
| 185 | static int run_test(struct test *test) | ||
| 186 | { | ||
| 187 | int status, err = -1, child = fork(); | ||
| 188 | |||
| 189 | if (child < 0) { | ||
| 190 | pr_err("failed to fork test: %s\n", strerror(errno)); | ||
| 191 | return -1; | ||
| 192 | } | ||
| 193 | |||
| 194 | if (!child) { | ||
| 195 | pr_debug("test child forked, pid %d\n", getpid()); | ||
| 196 | err = test->func(); | ||
| 197 | exit(err); | ||
| 198 | } | ||
| 199 | |||
| 200 | wait(&status); | ||
| 201 | |||
| 202 | if (WIFEXITED(status)) { | ||
| 203 | err = WEXITSTATUS(status); | ||
| 204 | pr_debug("test child finished with %d\n", err); | ||
| 205 | } else if (WIFSIGNALED(status)) { | ||
| 206 | err = -1; | ||
| 207 | pr_debug("test child interrupted\n"); | ||
| 208 | } | ||
| 209 | |||
| 210 | return err; | ||
| 211 | } | ||
| 212 | |||
| 175 | static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) | 213 | static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) |
| 176 | { | 214 | { |
| 177 | int i = 0; | 215 | int i = 0; |
| @@ -200,7 +238,7 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) | |||
| 200 | } | 238 | } |
| 201 | 239 | ||
| 202 | pr_debug("\n--- start ---\n"); | 240 | pr_debug("\n--- start ---\n"); |
| 203 | err = tests[curr].func(); | 241 | err = run_test(&tests[curr]); |
| 204 | pr_debug("---- end ----\n%s:", tests[curr].desc); | 242 | pr_debug("---- end ----\n%s:", tests[curr].desc); |
| 205 | 243 | ||
| 206 | switch (err) { | 244 | switch (err) { |
diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 3e6cb171e3d3..630808cd7cc2 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c | |||
| @@ -1,22 +1,27 @@ | |||
| 1 | #include "util.h" | ||
| 2 | |||
| 3 | #include <stdlib.h> | 1 | #include <stdlib.h> |
| 4 | #include <linux/types.h> | 2 | #include <linux/types.h> |
| 5 | #include <sys/stat.h> | 3 | #include <sys/stat.h> |
| 6 | #include <fcntl.h> | 4 | #include <fcntl.h> |
| 7 | #include <string.h> | 5 | #include <string.h> |
| 8 | 6 | #include <sys/time.h> | |
| 7 | #include <sys/resource.h> | ||
| 8 | #include <api/fs/fs.h> | ||
| 9 | #include "util.h" | ||
| 9 | #include "machine.h" | 10 | #include "machine.h" |
| 10 | #include "symbol.h" | 11 | #include "symbol.h" |
| 11 | #include "tests.h" | 12 | #include "tests.h" |
| 12 | 13 | ||
| 13 | static char *test_file(int size) | 14 | static char *test_file(int size) |
| 14 | { | 15 | { |
| 15 | static char buf_templ[] = "/tmp/test-XXXXXX"; | 16 | #define TEMPL "/tmp/perf-test-XXXXXX" |
| 17 | static char buf_templ[sizeof(TEMPL)]; | ||
| 16 | char *templ = buf_templ; | 18 | char *templ = buf_templ; |
| 17 | int fd, i; | 19 | int fd, i; |
| 18 | unsigned char *buf; | 20 | unsigned char *buf; |
| 19 | 21 | ||
| 22 | strcpy(buf_templ, TEMPL); | ||
| 23 | #undef TEMPL | ||
| 24 | |||
| 20 | fd = mkstemp(templ); | 25 | fd = mkstemp(templ); |
| 21 | if (fd < 0) { | 26 | if (fd < 0) { |
| 22 | perror("mkstemp failed"); | 27 | perror("mkstemp failed"); |
| @@ -150,3 +155,204 @@ int test__dso_data(void) | |||
| 150 | unlink(file); | 155 | unlink(file); |
| 151 | return 0; | 156 | return 0; |
| 152 | } | 157 | } |
| 158 | |||
| 159 | static long open_files_cnt(void) | ||
| 160 | { | ||
| 161 | char path[PATH_MAX]; | ||
| 162 | struct dirent *dent; | ||
| 163 | DIR *dir; | ||
| 164 | long nr = 0; | ||
| 165 | |||
| 166 | scnprintf(path, PATH_MAX, "%s/self/fd", procfs__mountpoint()); | ||
| 167 | pr_debug("fd path: %s\n", path); | ||
| 168 | |||
| 169 | dir = opendir(path); | ||
| 170 | TEST_ASSERT_VAL("failed to open fd directory", dir); | ||
| 171 | |||
| 172 | while ((dent = readdir(dir)) != NULL) { | ||
| 173 | if (!strcmp(dent->d_name, ".") || | ||
| 174 | !strcmp(dent->d_name, "..")) | ||
| 175 | continue; | ||
| 176 | |||
| 177 | nr++; | ||
| 178 | } | ||
| 179 | |||
| 180 | closedir(dir); | ||
| 181 | return nr - 1; | ||
| 182 | } | ||
| 183 | |||
| 184 | static struct dso **dsos; | ||
| 185 | |||
| 186 | static int dsos__create(int cnt, int size) | ||
| 187 | { | ||
| 188 | int i; | ||
| 189 | |||
| 190 | dsos = malloc(sizeof(dsos) * cnt); | ||
| 191 | TEST_ASSERT_VAL("failed to alloc dsos array", dsos); | ||
| 192 | |||
| 193 | for (i = 0; i < cnt; i++) { | ||
| 194 | char *file; | ||
| 195 | |||
| 196 | file = test_file(size); | ||
| 197 | TEST_ASSERT_VAL("failed to get dso file", file); | ||
| 198 | |||
| 199 | dsos[i] = dso__new(file); | ||
| 200 | TEST_ASSERT_VAL("failed to get dso", dsos[i]); | ||
| 201 | } | ||
| 202 | |||
| 203 | return 0; | ||
| 204 | } | ||
| 205 | |||
| 206 | static void dsos__delete(int cnt) | ||
| 207 | { | ||
| 208 | int i; | ||
| 209 | |||
| 210 | for (i = 0; i < cnt; i++) { | ||
| 211 | struct dso *dso = dsos[i]; | ||
| 212 | |||
| 213 | unlink(dso->name); | ||
| 214 | dso__delete(dso); | ||
| 215 | } | ||
| 216 | |||
| 217 | free(dsos); | ||
| 218 | } | ||
| 219 | |||
| 220 | static int set_fd_limit(int n) | ||
| 221 | { | ||
| 222 | struct rlimit rlim; | ||
| 223 | |||
| 224 | if (getrlimit(RLIMIT_NOFILE, &rlim)) | ||
| 225 | return -1; | ||
| 226 | |||
| 227 | pr_debug("file limit %ld, new %d\n", (long) rlim.rlim_cur, n); | ||
| 228 | |||
| 229 | rlim.rlim_cur = n; | ||
| 230 | return setrlimit(RLIMIT_NOFILE, &rlim); | ||
| 231 | } | ||
| 232 | |||
| 233 | int test__dso_data_cache(void) | ||
| 234 | { | ||
| 235 | struct machine machine; | ||
| 236 | long nr_end, nr = open_files_cnt(); | ||
| 237 | int dso_cnt, limit, i, fd; | ||
| 238 | |||
| 239 | memset(&machine, 0, sizeof(machine)); | ||
| 240 | |||
| 241 | /* set as system limit */ | ||
| 242 | limit = nr * 4; | ||
| 243 | TEST_ASSERT_VAL("failed to set file limit", !set_fd_limit(limit)); | ||
| 244 | |||
| 245 | /* and this is now our dso open FDs limit + 1 extra */ | ||
| 246 | dso_cnt = limit / 2 + 1; | ||
| 247 | TEST_ASSERT_VAL("failed to create dsos\n", | ||
| 248 | !dsos__create(dso_cnt, TEST_FILE_SIZE)); | ||
| 249 | |||
| 250 | for (i = 0; i < (dso_cnt - 1); i++) { | ||
| 251 | struct dso *dso = dsos[i]; | ||
| 252 | |||
| 253 | /* | ||
| 254 | * Open dsos via dso__data_fd or dso__data_read_offset. | ||
| 255 | * Both opens the data file and keep it open. | ||
| 256 | */ | ||
| 257 | if (i % 2) { | ||
| 258 | fd = dso__data_fd(dso, &machine); | ||
| 259 | TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||
| 260 | } else { | ||
| 261 | #define BUFSIZE 10 | ||
| 262 | u8 buf[BUFSIZE]; | ||
| 263 | ssize_t n; | ||
| 264 | |||
| 265 | n = dso__data_read_offset(dso, &machine, 0, buf, BUFSIZE); | ||
| 266 | TEST_ASSERT_VAL("failed to read dso", n == BUFSIZE); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /* open +1 dso over the allowed limit */ | ||
| 271 | fd = dso__data_fd(dsos[i], &machine); | ||
| 272 | TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||
| 273 | |||
| 274 | /* should force the first one to be closed */ | ||
| 275 | TEST_ASSERT_VAL("failed to close dsos[0]", dsos[0]->data.fd == -1); | ||
| 276 | |||
| 277 | /* cleanup everything */ | ||
| 278 | dsos__delete(dso_cnt); | ||
| 279 | |||
| 280 | /* Make sure we did not leak any file descriptor. */ | ||
| 281 | nr_end = open_files_cnt(); | ||
| 282 | pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); | ||
| 283 | TEST_ASSERT_VAL("failed leadking files", nr == nr_end); | ||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | int test__dso_data_reopen(void) | ||
| 288 | { | ||
| 289 | struct machine machine; | ||
| 290 | long nr_end, nr = open_files_cnt(); | ||
| 291 | int fd, fd_extra; | ||
| 292 | |||
| 293 | #define dso_0 (dsos[0]) | ||
| 294 | #define dso_1 (dsos[1]) | ||
| 295 | #define dso_2 (dsos[2]) | ||
| 296 | |||
| 297 | memset(&machine, 0, sizeof(machine)); | ||
| 298 | |||
| 299 | /* | ||
| 300 | * Test scenario: | ||
| 301 | * - create 3 dso objects | ||
| 302 | * - set process file descriptor limit to current | ||
| 303 | * files count + 3 | ||
| 304 | * - test that the first dso gets closed when we | ||
| 305 | * reach the files count limit | ||
| 306 | */ | ||
| 307 | |||
| 308 | /* Make sure we are able to open 3 fds anyway */ | ||
| 309 | TEST_ASSERT_VAL("failed to set file limit", | ||
| 310 | !set_fd_limit((nr + 3))); | ||
| 311 | |||
| 312 | TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE)); | ||
| 313 | |||
| 314 | /* open dso_0 */ | ||
| 315 | fd = dso__data_fd(dso_0, &machine); | ||
| 316 | TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||
| 317 | |||
| 318 | /* open dso_1 */ | ||
| 319 | fd = dso__data_fd(dso_1, &machine); | ||
| 320 | TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||
| 321 | |||
| 322 | /* | ||
| 323 | * open extra file descriptor and we just | ||
| 324 | * reached the files count limit | ||
| 325 | */ | ||
| 326 | fd_extra = open("/dev/null", O_RDONLY); | ||
| 327 | TEST_ASSERT_VAL("failed to open extra fd", fd_extra > 0); | ||
| 328 | |||
| 329 | /* open dso_2 */ | ||
| 330 | fd = dso__data_fd(dso_2, &machine); | ||
| 331 | TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||
| 332 | |||
| 333 | /* | ||
| 334 | * dso_0 should get closed, because we reached | ||
| 335 | * the file descriptor limit | ||
| 336 | */ | ||
| 337 | TEST_ASSERT_VAL("failed to close dso_0", dso_0->data.fd == -1); | ||
| 338 | |||
| 339 | /* open dso_0 */ | ||
| 340 | fd = dso__data_fd(dso_0, &machine); | ||
| 341 | TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||
| 342 | |||
| 343 | /* | ||
| 344 | * dso_1 should get closed, because we reached | ||
| 345 | * the file descriptor limit | ||
| 346 | */ | ||
| 347 | TEST_ASSERT_VAL("failed to close dso_1", dso_1->data.fd == -1); | ||
| 348 | |||
| 349 | /* cleanup everything */ | ||
| 350 | close(fd_extra); | ||
| 351 | dsos__delete(3); | ||
| 352 | |||
| 353 | /* Make sure we did not leak any file descriptor. */ | ||
| 354 | nr_end = open_files_cnt(); | ||
| 355 | pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); | ||
| 356 | TEST_ASSERT_VAL("failed leadking files", nr == nr_end); | ||
| 357 | return 0; | ||
| 358 | } | ||
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 022bb68fd9c7..ed64790a395f 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h | |||
| @@ -28,6 +28,8 @@ int test__syscall_open_tp_fields(void); | |||
| 28 | int test__pmu(void); | 28 | int test__pmu(void); |
| 29 | int test__attr(void); | 29 | int test__attr(void); |
| 30 | int test__dso_data(void); | 30 | int test__dso_data(void); |
| 31 | int test__dso_data_cache(void); | ||
| 32 | int test__dso_data_reopen(void); | ||
| 31 | int test__parse_events(void); | 33 | int test__parse_events(void); |
| 32 | int test__hists_link(void); | 34 | int test__hists_link(void); |
| 33 | int test__python_use(void); | 35 | int test__python_use(void); |
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 64453d63b971..819f10414f08 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c | |||
| @@ -1,3 +1,6 @@ | |||
| 1 | #include <asm/bug.h> | ||
| 2 | #include <sys/time.h> | ||
| 3 | #include <sys/resource.h> | ||
| 1 | #include "symbol.h" | 4 | #include "symbol.h" |
| 2 | #include "dso.h" | 5 | #include "dso.h" |
| 3 | #include "machine.h" | 6 | #include "machine.h" |
| @@ -136,7 +139,48 @@ int dso__read_binary_type_filename(const struct dso *dso, | |||
| 136 | return ret; | 139 | return ret; |
| 137 | } | 140 | } |
| 138 | 141 | ||
| 139 | static int open_dso(struct dso *dso, struct machine *machine) | 142 | /* |
| 143 | * Global list of open DSOs and the counter. | ||
| 144 | */ | ||
| 145 | static LIST_HEAD(dso__data_open); | ||
| 146 | static long dso__data_open_cnt; | ||
| 147 | |||
| 148 | static void dso__list_add(struct dso *dso) | ||
| 149 | { | ||
| 150 | list_add_tail(&dso->data.open_entry, &dso__data_open); | ||
| 151 | dso__data_open_cnt++; | ||
| 152 | } | ||
| 153 | |||
| 154 | static void dso__list_del(struct dso *dso) | ||
| 155 | { | ||
| 156 | list_del(&dso->data.open_entry); | ||
| 157 | WARN_ONCE(dso__data_open_cnt <= 0, | ||
| 158 | "DSO data fd counter out of bounds."); | ||
| 159 | dso__data_open_cnt--; | ||
| 160 | } | ||
| 161 | |||
| 162 | static void close_first_dso(void); | ||
| 163 | |||
| 164 | static int do_open(char *name) | ||
| 165 | { | ||
| 166 | int fd; | ||
| 167 | |||
| 168 | do { | ||
| 169 | fd = open(name, O_RDONLY); | ||
| 170 | if (fd >= 0) | ||
| 171 | return fd; | ||
| 172 | |||
| 173 | pr_debug("dso open failed, mmap: %s\n", strerror(errno)); | ||
| 174 | if (!dso__data_open_cnt || errno != EMFILE) | ||
| 175 | break; | ||
| 176 | |||
| 177 | close_first_dso(); | ||
| 178 | } while (1); | ||
| 179 | |||
| 180 | return -1; | ||
| 181 | } | ||
| 182 | |||
| 183 | static int __open_dso(struct dso *dso, struct machine *machine) | ||
| 140 | { | 184 | { |
| 141 | int fd; | 185 | int fd; |
| 142 | char *root_dir = (char *)""; | 186 | char *root_dir = (char *)""; |
| @@ -154,11 +198,130 @@ static int open_dso(struct dso *dso, struct machine *machine) | |||
| 154 | return -EINVAL; | 198 | return -EINVAL; |
| 155 | } | 199 | } |
| 156 | 200 | ||
| 157 | fd = open(name, O_RDONLY); | 201 | fd = do_open(name); |
| 158 | free(name); | 202 | free(name); |
| 159 | return fd; | 203 | return fd; |
| 160 | } | 204 | } |
| 161 | 205 | ||
| 206 | static void check_data_close(void); | ||
| 207 | |||
| 208 | /** | ||
| 209 | * dso_close - Open DSO data file | ||
| 210 | * @dso: dso object | ||
| 211 | * | ||
| 212 | * Open @dso's data file descriptor and updates | ||
| 213 | * list/count of open DSO objects. | ||
| 214 | */ | ||
| 215 | static int open_dso(struct dso *dso, struct machine *machine) | ||
| 216 | { | ||
| 217 | int fd = __open_dso(dso, machine); | ||
| 218 | |||
| 219 | if (fd > 0) { | ||
| 220 | dso__list_add(dso); | ||
| 221 | /* | ||
| 222 | * Check if we crossed the allowed number | ||
| 223 | * of opened DSOs and close one if needed. | ||
| 224 | */ | ||
| 225 | check_data_close(); | ||
| 226 | } | ||
| 227 | |||
| 228 | return fd; | ||
| 229 | } | ||
| 230 | |||
| 231 | static void close_data_fd(struct dso *dso) | ||
| 232 | { | ||
| 233 | if (dso->data.fd >= 0) { | ||
| 234 | close(dso->data.fd); | ||
| 235 | dso->data.fd = -1; | ||
| 236 | dso->data.file_size = 0; | ||
| 237 | dso__list_del(dso); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | /** | ||
| 242 | * dso_close - Close DSO data file | ||
| 243 | * @dso: dso object | ||
| 244 | * | ||
| 245 | * Close @dso's data file descriptor and updates | ||
| 246 | * list/count of open DSO objects. | ||
| 247 | */ | ||
| 248 | static void close_dso(struct dso *dso) | ||
| 249 | { | ||
| 250 | close_data_fd(dso); | ||
| 251 | } | ||
| 252 | |||
| 253 | static void close_first_dso(void) | ||
| 254 | { | ||
| 255 | struct dso *dso; | ||
| 256 | |||
| 257 | dso = list_first_entry(&dso__data_open, struct dso, data.open_entry); | ||
| 258 | close_dso(dso); | ||
| 259 | } | ||
| 260 | |||
| 261 | static rlim_t get_fd_limit(void) | ||
| 262 | { | ||
| 263 | struct rlimit l; | ||
| 264 | rlim_t limit = 0; | ||
| 265 | |||
| 266 | /* Allow half of the current open fd limit. */ | ||
| 267 | if (getrlimit(RLIMIT_NOFILE, &l) == 0) { | ||
| 268 | if (l.rlim_cur == RLIM_INFINITY) | ||
| 269 | limit = l.rlim_cur; | ||
| 270 | else | ||
| 271 | limit = l.rlim_cur / 2; | ||
| 272 | } else { | ||
| 273 | pr_err("failed to get fd limit\n"); | ||
| 274 | limit = 1; | ||
| 275 | } | ||
| 276 | |||
| 277 | return limit; | ||
| 278 | } | ||
| 279 | |||
| 280 | static bool may_cache_fd(void) | ||
| 281 | { | ||
| 282 | static rlim_t limit; | ||
| 283 | |||
| 284 | if (!limit) | ||
| 285 | limit = get_fd_limit(); | ||
| 286 | |||
| 287 | if (limit == RLIM_INFINITY) | ||
| 288 | return true; | ||
| 289 | |||
| 290 | return limit > (rlim_t) dso__data_open_cnt; | ||
| 291 | } | ||
| 292 | |||
| 293 | /* | ||
| 294 | * Check and close LRU dso if we crossed allowed limit | ||
| 295 | * for opened dso file descriptors. The limit is half | ||
| 296 | * of the RLIMIT_NOFILE files opened. | ||
| 297 | */ | ||
| 298 | static void check_data_close(void) | ||
| 299 | { | ||
| 300 | bool cache_fd = may_cache_fd(); | ||
| 301 | |||
| 302 | if (!cache_fd) | ||
| 303 | close_first_dso(); | ||
| 304 | } | ||
| 305 | |||
| 306 | /** | ||
| 307 | * dso__data_close - Close DSO data file | ||
| 308 | * @dso: dso object | ||
| 309 | * | ||
| 310 | * External interface to close @dso's data file descriptor. | ||
| 311 | */ | ||
| 312 | void dso__data_close(struct dso *dso) | ||
| 313 | { | ||
| 314 | close_dso(dso); | ||
| 315 | } | ||
| 316 | |||
| 317 | /** | ||
| 318 | * dso__data_fd - Get dso's data file descriptor | ||
| 319 | * @dso: dso object | ||
| 320 | * @machine: machine object | ||
| 321 | * | ||
| 322 | * External interface to find dso's file, open it and | ||
| 323 | * returns file descriptor. | ||
| 324 | */ | ||
| 162 | int dso__data_fd(struct dso *dso, struct machine *machine) | 325 | int dso__data_fd(struct dso *dso, struct machine *machine) |
| 163 | { | 326 | { |
| 164 | enum dso_binary_type binary_type_data[] = { | 327 | enum dso_binary_type binary_type_data[] = { |
| @@ -168,8 +331,13 @@ int dso__data_fd(struct dso *dso, struct machine *machine) | |||
| 168 | }; | 331 | }; |
| 169 | int i = 0; | 332 | int i = 0; |
| 170 | 333 | ||
| 171 | if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) | 334 | if (dso->data.fd >= 0) |
| 172 | return open_dso(dso, machine); | 335 | return dso->data.fd; |
| 336 | |||
| 337 | if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) { | ||
| 338 | dso->data.fd = open_dso(dso, machine); | ||
| 339 | return dso->data.fd; | ||
| 340 | } | ||
| 173 | 341 | ||
| 174 | do { | 342 | do { |
| 175 | int fd; | 343 | int fd; |
| @@ -178,7 +346,7 @@ int dso__data_fd(struct dso *dso, struct machine *machine) | |||
| 178 | 346 | ||
| 179 | fd = open_dso(dso, machine); | 347 | fd = open_dso(dso, machine); |
| 180 | if (fd >= 0) | 348 | if (fd >= 0) |
| 181 | return fd; | 349 | return dso->data.fd = fd; |
| 182 | 350 | ||
| 183 | } while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND); | 351 | } while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND); |
| 184 | 352 | ||
| @@ -260,16 +428,10 @@ dso_cache__memcpy(struct dso_cache *cache, u64 offset, | |||
| 260 | } | 428 | } |
| 261 | 429 | ||
| 262 | static ssize_t | 430 | static ssize_t |
| 263 | dso_cache__read(struct dso *dso, struct machine *machine, | 431 | dso_cache__read(struct dso *dso, u64 offset, u8 *data, ssize_t size) |
| 264 | u64 offset, u8 *data, ssize_t size) | ||
| 265 | { | 432 | { |
| 266 | struct dso_cache *cache; | 433 | struct dso_cache *cache; |
| 267 | ssize_t ret; | 434 | ssize_t ret; |
| 268 | int fd; | ||
| 269 | |||
| 270 | fd = dso__data_fd(dso, machine); | ||
| 271 | if (fd < 0) | ||
| 272 | return -1; | ||
| 273 | 435 | ||
| 274 | do { | 436 | do { |
| 275 | u64 cache_offset; | 437 | u64 cache_offset; |
| @@ -283,16 +445,16 @@ dso_cache__read(struct dso *dso, struct machine *machine, | |||
| 283 | cache_offset = offset & DSO__DATA_CACHE_MASK; | 445 | cache_offset = offset & DSO__DATA_CACHE_MASK; |
| 284 | ret = -EINVAL; | 446 | ret = -EINVAL; |
| 285 | 447 | ||
| 286 | if (-1 == lseek(fd, cache_offset, SEEK_SET)) | 448 | if (-1 == lseek(dso->data.fd, cache_offset, SEEK_SET)) |
| 287 | break; | 449 | break; |
| 288 | 450 | ||
| 289 | ret = read(fd, cache->data, DSO__DATA_CACHE_SIZE); | 451 | ret = read(dso->data.fd, cache->data, DSO__DATA_CACHE_SIZE); |
| 290 | if (ret <= 0) | 452 | if (ret <= 0) |
| 291 | break; | 453 | break; |
| 292 | 454 | ||
| 293 | cache->offset = cache_offset; | 455 | cache->offset = cache_offset; |
| 294 | cache->size = ret; | 456 | cache->size = ret; |
| 295 | dso_cache__insert(&dso->cache, cache); | 457 | dso_cache__insert(&dso->data.cache, cache); |
| 296 | 458 | ||
| 297 | ret = dso_cache__memcpy(cache, offset, data, size); | 459 | ret = dso_cache__memcpy(cache, offset, data, size); |
| 298 | 460 | ||
| @@ -301,24 +463,27 @@ dso_cache__read(struct dso *dso, struct machine *machine, | |||
| 301 | if (ret <= 0) | 463 | if (ret <= 0) |
| 302 | free(cache); | 464 | free(cache); |
| 303 | 465 | ||
| 304 | close(fd); | ||
| 305 | return ret; | 466 | return ret; |
| 306 | } | 467 | } |
| 307 | 468 | ||
| 308 | static ssize_t dso_cache_read(struct dso *dso, struct machine *machine, | 469 | static ssize_t dso_cache_read(struct dso *dso, u64 offset, |
| 309 | u64 offset, u8 *data, ssize_t size) | 470 | u8 *data, ssize_t size) |
| 310 | { | 471 | { |
| 311 | struct dso_cache *cache; | 472 | struct dso_cache *cache; |
| 312 | 473 | ||
| 313 | cache = dso_cache__find(&dso->cache, offset); | 474 | cache = dso_cache__find(&dso->data.cache, offset); |
| 314 | if (cache) | 475 | if (cache) |
| 315 | return dso_cache__memcpy(cache, offset, data, size); | 476 | return dso_cache__memcpy(cache, offset, data, size); |
| 316 | else | 477 | else |
| 317 | return dso_cache__read(dso, machine, offset, data, size); | 478 | return dso_cache__read(dso, offset, data, size); |
| 318 | } | 479 | } |
| 319 | 480 | ||
| 320 | ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | 481 | /* |
| 321 | u64 offset, u8 *data, ssize_t size) | 482 | * Reads and caches dso data DSO__DATA_CACHE_SIZE size chunks |
| 483 | * in the rb_tree. Any read to already cached data is served | ||
| 484 | * by cached data. | ||
| 485 | */ | ||
| 486 | static ssize_t cached_read(struct dso *dso, u64 offset, u8 *data, ssize_t size) | ||
| 322 | { | 487 | { |
| 323 | ssize_t r = 0; | 488 | ssize_t r = 0; |
| 324 | u8 *p = data; | 489 | u8 *p = data; |
| @@ -326,7 +491,7 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | |||
| 326 | do { | 491 | do { |
| 327 | ssize_t ret; | 492 | ssize_t ret; |
| 328 | 493 | ||
| 329 | ret = dso_cache_read(dso, machine, offset, p, size); | 494 | ret = dso_cache_read(dso, offset, p, size); |
| 330 | if (ret < 0) | 495 | if (ret < 0) |
| 331 | return ret; | 496 | return ret; |
| 332 | 497 | ||
| @@ -346,6 +511,67 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | |||
| 346 | return r; | 511 | return r; |
| 347 | } | 512 | } |
| 348 | 513 | ||
| 514 | static int data_file_size(struct dso *dso) | ||
| 515 | { | ||
| 516 | struct stat st; | ||
| 517 | |||
| 518 | if (!dso->data.file_size) { | ||
| 519 | if (fstat(dso->data.fd, &st)) { | ||
| 520 | pr_err("dso mmap failed, fstat: %s\n", strerror(errno)); | ||
| 521 | return -1; | ||
| 522 | } | ||
| 523 | dso->data.file_size = st.st_size; | ||
| 524 | } | ||
| 525 | |||
| 526 | return 0; | ||
| 527 | } | ||
| 528 | |||
| 529 | static ssize_t data_read_offset(struct dso *dso, u64 offset, | ||
| 530 | u8 *data, ssize_t size) | ||
| 531 | { | ||
| 532 | if (data_file_size(dso)) | ||
| 533 | return -1; | ||
| 534 | |||
| 535 | /* Check the offset sanity. */ | ||
| 536 | if (offset > dso->data.file_size) | ||
| 537 | return -1; | ||
| 538 | |||
| 539 | if (offset + size < offset) | ||
| 540 | return -1; | ||
| 541 | |||
| 542 | return cached_read(dso, offset, data, size); | ||
| 543 | } | ||
| 544 | |||
| 545 | /** | ||
| 546 | * dso__data_read_offset - Read data from dso file offset | ||
| 547 | * @dso: dso object | ||
| 548 | * @machine: machine object | ||
| 549 | * @offset: file offset | ||
| 550 | * @data: buffer to store data | ||
| 551 | * @size: size of the @data buffer | ||
| 552 | * | ||
| 553 | * External interface to read data from dso file offset. Open | ||
| 554 | * dso data file and use cached_read to get the data. | ||
| 555 | */ | ||
| 556 | ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | ||
| 557 | u64 offset, u8 *data, ssize_t size) | ||
| 558 | { | ||
| 559 | if (dso__data_fd(dso, machine) < 0) | ||
| 560 | return -1; | ||
| 561 | |||
| 562 | return data_read_offset(dso, offset, data, size); | ||
| 563 | } | ||
| 564 | |||
| 565 | /** | ||
| 566 | * dso__data_read_addr - Read data from dso address | ||
| 567 | * @dso: dso object | ||
| 568 | * @machine: machine object | ||
| 569 | * @add: virtual memory address | ||
| 570 | * @data: buffer to store data | ||
| 571 | * @size: size of the @data buffer | ||
| 572 | * | ||
| 573 | * External interface to read data from dso address. | ||
| 574 | */ | ||
| 349 | ssize_t dso__data_read_addr(struct dso *dso, struct map *map, | 575 | ssize_t dso__data_read_addr(struct dso *dso, struct map *map, |
| 350 | struct machine *machine, u64 addr, | 576 | struct machine *machine, u64 addr, |
| 351 | u8 *data, ssize_t size) | 577 | u8 *data, ssize_t size) |
| @@ -473,7 +699,8 @@ struct dso *dso__new(const char *name) | |||
| 473 | dso__set_short_name(dso, dso->name, false); | 699 | dso__set_short_name(dso, dso->name, false); |
| 474 | for (i = 0; i < MAP__NR_TYPES; ++i) | 700 | for (i = 0; i < MAP__NR_TYPES; ++i) |
| 475 | dso->symbols[i] = dso->symbol_names[i] = RB_ROOT; | 701 | dso->symbols[i] = dso->symbol_names[i] = RB_ROOT; |
| 476 | dso->cache = RB_ROOT; | 702 | dso->data.cache = RB_ROOT; |
| 703 | dso->data.fd = -1; | ||
| 477 | dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; | 704 | dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; |
| 478 | dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; | 705 | dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; |
| 479 | dso->loaded = 0; | 706 | dso->loaded = 0; |
| @@ -485,6 +712,7 @@ struct dso *dso__new(const char *name) | |||
| 485 | dso->kernel = DSO_TYPE_USER; | 712 | dso->kernel = DSO_TYPE_USER; |
| 486 | dso->needs_swap = DSO_SWAP__UNSET; | 713 | dso->needs_swap = DSO_SWAP__UNSET; |
| 487 | INIT_LIST_HEAD(&dso->node); | 714 | INIT_LIST_HEAD(&dso->node); |
| 715 | INIT_LIST_HEAD(&dso->data.open_entry); | ||
| 488 | } | 716 | } |
| 489 | 717 | ||
| 490 | return dso; | 718 | return dso; |
| @@ -506,7 +734,8 @@ void dso__delete(struct dso *dso) | |||
| 506 | dso->long_name_allocated = false; | 734 | dso->long_name_allocated = false; |
| 507 | } | 735 | } |
| 508 | 736 | ||
| 509 | dso_cache__free(&dso->cache); | 737 | dso__data_close(dso); |
| 738 | dso_cache__free(&dso->data.cache); | ||
| 510 | dso__free_a2l(dso); | 739 | dso__free_a2l(dso); |
| 511 | zfree(&dso->symsrc_filename); | 740 | zfree(&dso->symsrc_filename); |
| 512 | free(dso); | 741 | free(dso); |
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 38efe95a7fdd..ad553ba257bf 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h | |||
| @@ -76,7 +76,6 @@ struct dso { | |||
| 76 | struct list_head node; | 76 | struct list_head node; |
| 77 | struct rb_root symbols[MAP__NR_TYPES]; | 77 | struct rb_root symbols[MAP__NR_TYPES]; |
| 78 | struct rb_root symbol_names[MAP__NR_TYPES]; | 78 | struct rb_root symbol_names[MAP__NR_TYPES]; |
| 79 | struct rb_root cache; | ||
| 80 | void *a2l; | 79 | void *a2l; |
| 81 | char *symsrc_filename; | 80 | char *symsrc_filename; |
| 82 | unsigned int a2l_fails; | 81 | unsigned int a2l_fails; |
| @@ -99,6 +98,15 @@ struct dso { | |||
| 99 | const char *long_name; | 98 | const char *long_name; |
| 100 | u16 long_name_len; | 99 | u16 long_name_len; |
| 101 | u16 short_name_len; | 100 | u16 short_name_len; |
| 101 | |||
| 102 | /* dso data file */ | ||
| 103 | struct { | ||
| 104 | struct rb_root cache; | ||
| 105 | int fd; | ||
| 106 | size_t file_size; | ||
| 107 | struct list_head open_entry; | ||
| 108 | } data; | ||
| 109 | |||
| 102 | char name[0]; | 110 | char name[0]; |
| 103 | }; | 111 | }; |
| 104 | 112 | ||
| @@ -141,7 +149,47 @@ char dso__symtab_origin(const struct dso *dso); | |||
| 141 | int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, | 149 | int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, |
| 142 | char *root_dir, char *filename, size_t size); | 150 | char *root_dir, char *filename, size_t size); |
| 143 | 151 | ||
| 152 | /* | ||
| 153 | * The dso__data_* external interface provides following functions: | ||
| 154 | * dso__data_fd | ||
| 155 | * dso__data_close | ||
| 156 | * dso__data_read_offset | ||
| 157 | * dso__data_read_addr | ||
| 158 | * | ||
| 159 | * Please refer to the dso.c object code for each function and | ||
| 160 | * arguments documentation. Following text tries to explain the | ||
| 161 | * dso file descriptor caching. | ||
| 162 | * | ||
| 163 | * The dso__data* interface allows caching of opened file descriptors | ||
| 164 | * to speed up the dso data accesses. The idea is to leave the file | ||
| 165 | * descriptor opened ideally for the whole life of the dso object. | ||
| 166 | * | ||
| 167 | * The current usage of the dso__data_* interface is as follows: | ||
| 168 | * | ||
| 169 | * Get DSO's fd: | ||
| 170 | * int fd = dso__data_fd(dso, machine); | ||
| 171 | * USE 'fd' SOMEHOW | ||
| 172 | * | ||
| 173 | * Read DSO's data: | ||
| 174 | * n = dso__data_read_offset(dso_0, &machine, 0, buf, BUFSIZE); | ||
| 175 | * n = dso__data_read_addr(dso_0, &machine, 0, buf, BUFSIZE); | ||
| 176 | * | ||
| 177 | * Eventually close DSO's fd: | ||
| 178 | * dso__data_close(dso); | ||
| 179 | * | ||
| 180 | * It is not necessary to close the DSO object data file. Each time new | ||
| 181 | * DSO data file is opened, the limit (RLIMIT_NOFILE/2) is checked. Once | ||
| 182 | * it is crossed, the oldest opened DSO object is closed. | ||
| 183 | * | ||
| 184 | * The dso__delete function calls close_dso function to ensure the | ||
| 185 | * data file descriptor gets closed/unmapped before the dso object | ||
| 186 | * is freed. | ||
| 187 | * | ||
| 188 | * TODO | ||
| 189 | */ | ||
| 144 | int dso__data_fd(struct dso *dso, struct machine *machine); | 190 | int dso__data_fd(struct dso *dso, struct machine *machine); |
| 191 | void dso__data_close(struct dso *dso); | ||
| 192 | |||
| 145 | ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | 193 | ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, |
| 146 | u64 offset, u8 *data, ssize_t size); | 194 | u64 offset, u8 *data, ssize_t size); |
| 147 | ssize_t dso__data_read_addr(struct dso *dso, struct map *map, | 195 | ssize_t dso__data_read_addr(struct dso *dso, struct map *map, |
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 9ba2eb3bdcfd..e5dd40addb30 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include "../perf.h" | 7 | #include "../perf.h" |
| 8 | #include "map.h" | 8 | #include "map.h" |
| 9 | #include "build-id.h" | 9 | #include "build-id.h" |
| 10 | #include "perf_regs.h" | ||
| 10 | 11 | ||
| 11 | struct mmap_event { | 12 | struct mmap_event { |
| 12 | struct perf_event_header header; | 13 | struct perf_event_header header; |
| @@ -89,6 +90,10 @@ struct regs_dump { | |||
| 89 | u64 abi; | 90 | u64 abi; |
| 90 | u64 mask; | 91 | u64 mask; |
| 91 | u64 *regs; | 92 | u64 *regs; |
| 93 | |||
| 94 | /* Cached values/mask filled by first register access. */ | ||
| 95 | u64 cache_regs[PERF_REGS_MAX]; | ||
| 96 | u64 cache_mask; | ||
| 92 | }; | 97 | }; |
| 93 | 98 | ||
| 94 | struct stack_dump { | 99 | struct stack_dump { |
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 21154dabc5fa..8606175fe1e8 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c | |||
| @@ -589,10 +589,10 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) | |||
| 589 | } | 589 | } |
| 590 | 590 | ||
| 591 | /* | 591 | /* |
| 592 | * We default some events to a 1 default interval. But keep | 592 | * We default some events to have a default interval. But keep |
| 593 | * it a weak assumption overridable by the user. | 593 | * it a weak assumption overridable by the user. |
| 594 | */ | 594 | */ |
| 595 | if (!attr->sample_period || (opts->user_freq != UINT_MAX && | 595 | if (!attr->sample_period || (opts->user_freq != UINT_MAX || |
| 596 | opts->user_interval != ULLONG_MAX)) { | 596 | opts->user_interval != ULLONG_MAX)) { |
| 597 | if (opts->freq) { | 597 | if (opts->freq) { |
| 598 | perf_evsel__set_sample_bit(evsel, PERIOD); | 598 | perf_evsel__set_sample_bit(evsel, PERIOD); |
diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index a3539ef30b15..43168fb0d9a2 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c | |||
| @@ -1,11 +1,15 @@ | |||
| 1 | #include <errno.h> | 1 | #include <errno.h> |
| 2 | #include "perf_regs.h" | 2 | #include "perf_regs.h" |
| 3 | #include "event.h" | ||
| 3 | 4 | ||
| 4 | int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) | 5 | int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) |
| 5 | { | 6 | { |
| 6 | int i, idx = 0; | 7 | int i, idx = 0; |
| 7 | u64 mask = regs->mask; | 8 | u64 mask = regs->mask; |
| 8 | 9 | ||
| 10 | if (regs->cache_mask & (1 << id)) | ||
| 11 | goto out; | ||
| 12 | |||
| 9 | if (!(mask & (1 << id))) | 13 | if (!(mask & (1 << id))) |
| 10 | return -EINVAL; | 14 | return -EINVAL; |
| 11 | 15 | ||
| @@ -14,6 +18,10 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) | |||
| 14 | idx++; | 18 | idx++; |
| 15 | } | 19 | } |
| 16 | 20 | ||
| 17 | *valp = regs->regs[idx]; | 21 | regs->cache_mask |= (1 << id); |
| 22 | regs->cache_regs[id] = regs->regs[idx]; | ||
| 23 | |||
| 24 | out: | ||
| 25 | *valp = regs->cache_regs[id]; | ||
| 18 | return 0; | 26 | return 0; |
| 19 | } | 27 | } |
diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index 79c78f74e0cf..980dbf76bc98 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h | |||
| @@ -2,7 +2,8 @@ | |||
| 2 | #define __PERF_REGS_H | 2 | #define __PERF_REGS_H |
| 3 | 3 | ||
| 4 | #include <linux/types.h> | 4 | #include <linux/types.h> |
| 5 | #include "event.h" | 5 | |
| 6 | struct regs_dump; | ||
| 6 | 7 | ||
| 7 | #ifdef HAVE_PERF_REGS_SUPPORT | 8 | #ifdef HAVE_PERF_REGS_SUPPORT |
| 8 | #include <perf_regs.h> | 9 | #include <perf_regs.h> |
| @@ -11,6 +12,7 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id); | |||
| 11 | 12 | ||
| 12 | #else | 13 | #else |
| 13 | #define PERF_REGS_MASK 0 | 14 | #define PERF_REGS_MASK 0 |
| 15 | #define PERF_REGS_MAX 0 | ||
| 14 | 16 | ||
| 15 | static inline const char *perf_reg_name(int id __maybe_unused) | 17 | static inline const char *perf_reg_name(int id __maybe_unused) |
| 16 | { | 18 | { |
diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c index bd5768d74f01..25578b98f5c5 100644 --- a/tools/perf/util/unwind-libunwind.c +++ b/tools/perf/util/unwind-libunwind.c | |||
| @@ -250,7 +250,6 @@ static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine, | |||
| 250 | 250 | ||
| 251 | /* Check the .eh_frame section for unwinding info */ | 251 | /* Check the .eh_frame section for unwinding info */ |
| 252 | offset = elf_section_offset(fd, ".eh_frame_hdr"); | 252 | offset = elf_section_offset(fd, ".eh_frame_hdr"); |
| 253 | close(fd); | ||
| 254 | 253 | ||
| 255 | if (offset) | 254 | if (offset) |
| 256 | ret = unwind_spec_ehframe(dso, machine, offset, | 255 | ret = unwind_spec_ehframe(dso, machine, offset, |
| @@ -271,7 +270,6 @@ static int read_unwind_spec_debug_frame(struct dso *dso, | |||
| 271 | 270 | ||
| 272 | /* Check the .debug_frame section for unwinding info */ | 271 | /* Check the .debug_frame section for unwinding info */ |
| 273 | *offset = elf_section_offset(fd, ".debug_frame"); | 272 | *offset = elf_section_offset(fd, ".debug_frame"); |
| 274 | close(fd); | ||
| 275 | 273 | ||
| 276 | if (*offset) | 274 | if (*offset) |
| 277 | return 0; | 275 | return 0; |
