diff options
author | Stewart Smith <stewart@linux.vnet.ibm.com> | 2015-05-07 17:11:46 +1000 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2015-05-15 07:59:18 +1000 |
commit | d3cedfba5553227dec328ed0000221ed5c4294ad (patch) | |
tree | 70be07441f6b139779792e95588c59c43712468a /extract-gcov.c | |
parent | 614aa6a7833839146de2769a09de355a49b8ed3d (diff) | |
download | skiboot-d3cedfba5553227dec328ed0000221ed5c4294ad.zip skiboot-d3cedfba5553227dec328ed0000221ed5c4294ad.tar.gz skiboot-d3cedfba5553227dec328ed0000221ed5c4294ad.tar.bz2 |
Add extract-gcov utility for extracting gcda from skiboot dump
If you dump the (relocated) skiboot memory (2MB, from 0x30000000)
and point extract-gcov at it, you'll get a bunch of gcda files
extracted in pwd that you can then feed to gcov to get real usage
data.
This is a different approach than, say, the linux kernel, which when
built with gcov provides a debugfs interface to the gcda files that
you can just copy with normal userspace utilities.
For skiboot, I have no desire to add a VFS style interface and since
if you're dealing with GCOV+skiboot you should probably pretty well
know what you're doing, parsing the gcov data structures in userspace
from a dump of memory is actually not too bad.
You can grab this memory from linux, FSP, mambo or any debug mechanism that
lets you dump out a section of physical memory.
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'extract-gcov.c')
-rw-r--r-- | extract-gcov.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/extract-gcov.c b/extract-gcov.c new file mode 100644 index 0000000..dfbc961 --- /dev/null +++ b/extract-gcov.c @@ -0,0 +1,220 @@ +/* Copyright 2015 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _BSD_SOURCE +#include <ccan/short_types/short_types.h> +#include <endian.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <assert.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +typedef unsigned int gcov_unsigned_int; + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 9 +#define GCOV_COUNTERS 9 +#else +#define GCOV_COUNTERS 8 +#endif +typedef u64 gcov_type; + +struct gcov_info +{ + gcov_unsigned_int version; + struct gcov_info *next; + gcov_unsigned_int stamp; + const char *filename; + void (*merge[GCOV_COUNTERS])(gcov_type *, unsigned int); + unsigned int n_functions; + struct gcov_fn_info **functions; +}; + +struct gcov_ctr_info { + gcov_unsigned_int num; + gcov_type *values; +}; + +struct gcov_fn_info { + const struct gcov_info *key; + unsigned int ident; + unsigned int lineno_checksum; + unsigned int cfg_checksum; +// struct gcov_ctr_info ctrs[0]; +}; + + +/* We have a list of all gcov info set up at startup */ +struct gcov_info *gcov_info_list; + +#define SKIBOOT_OFFSET 0x30000000 + +/* Endian of the machine producing the gcda. Which mean BE. + * because skiboot is BE. + * If skiboot is ever LE, go have fun. + */ +static size_t write_u32(int fd, u32 _v) +{ + u32 v = htobe32(_v); + return write(fd, &v, sizeof(v)); +} + +static size_t write_u64(int fd, u64 v) +{ + u32 b[2]; + b[0] = htobe32(v & 0xffffffffUL); + b[1] = htobe32(v >> 32); + + write(fd, &b[0], sizeof(u32)); + write(fd, &b[1], sizeof(u32)); + return sizeof(u64); +} + +#define GCOV_DATA_MAGIC ((unsigned int) 0x67636461) +#define GCOV_TAG_FUNCTION ((unsigned int) 0x01000000) +#define GCOV_TAG_COUNTER_BASE ((unsigned int) 0x01a10000) +#define GCOV_TAG_FOR_COUNTER(count) \ + (GCOV_TAG_COUNTER_BASE + ((unsigned int) (count) << 17)) + +// gcc 4.7/4.8 specific +#define GCOV_TAG_FUNCTION_LENGTH 3 + +static inline const char* SKIBOOT_ADDR(const char* addr, const void* p) +{ + const char* r= (addr + (be64toh((const u64)p) - SKIBOOT_OFFSET)); + assert(r < (addr + 0x240000)); + return r; +} + +static int counter_active(struct gcov_info *info, unsigned int type) +{ + return info->merge[type] ? 1 : 0; +} + +static void write_gcda(char *addr, struct gcov_info* gi) +{ + const char* filename = SKIBOOT_ADDR(addr, gi->filename); + int fd; + u32 fn; + struct gcov_fn_info *fn_info; + struct gcov_fn_info **functions; + struct gcov_ctr_info *ctr_info; + u32 ctr; + u32 cv; + + printf("Writing %s\n", filename); + + fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); + write_u32(fd, GCOV_DATA_MAGIC); + write_u32(fd, be32toh(gi->version)); + write_u32(fd, be32toh(gi->stamp)); + + printf("nfunctions: %d \n", be32toh(gi->n_functions)); + + for(fn = 0; fn < be32toh(gi->n_functions); fn++) { + functions = (struct gcov_fn_info**) + SKIBOOT_ADDR(addr, gi->functions); + + fn_info = (struct gcov_fn_info*) + SKIBOOT_ADDR(addr, functions[fn]); + + write_u32(fd, GCOV_TAG_FUNCTION); + write_u32(fd, GCOV_TAG_FUNCTION_LENGTH); + write_u32(fd, be32toh(fn_info->ident)); + write_u32(fd, be32toh(fn_info->lineno_checksum)); + write_u32(fd, be32toh(fn_info->cfg_checksum)); + + ctr_info = (struct gcov_ctr_info*) + ((char*)fn_info + sizeof(struct gcov_fn_info)); + + for(ctr = 0; ctr < GCOV_COUNTERS; ctr++) { + if (!counter_active(gi, ctr)) + continue; + + write_u32(fd, (GCOV_TAG_FOR_COUNTER(ctr))); + write_u32(fd, be32toh(ctr_info->num)*2); + printf(" ctr %d gcov_ctr_info->num %u\n", + ctr, be32toh(ctr_info->num)); + + for(cv = 0; cv < be32toh(ctr_info->num); cv++) { + gcov_type *ctrv = (gcov_type *) + SKIBOOT_ADDR(addr, ctr_info->values); + printf("%lx\n", be64toh(ctrv[cv])); + write_u64(fd, be64toh(ctrv[cv])); + } + ctr_info++; + } + } + + close(fd); +} + + +int main(int argc, char *argv[]) +{ + int r; + int fd; + struct stat sb; + char *addr; + u64 gcov_list_addr; + + printf("sizes: %zu %zu %zu %zu\n", + sizeof(gcov_unsigned_int), + sizeof(struct gcov_ctr_info), + sizeof(struct gcov_fn_info), + sizeof(struct gcov_info)); + + if (argc < 3) { + fprintf(stderr, "Usage:\n" + "\t%s skiboot.dump gcov_offset\n\n", + argv[0]); + return -1; + } + + /* argv[1] = skiboot.dump */ + fd = open(argv[1], O_RDONLY); + assert(fd > 0); + + r = fstat(fd, &sb); + assert(r != -1); + + addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + assert(addr != NULL); + + printf("Skiboot memory dump %p - %p\n", + (void*)SKIBOOT_OFFSET, (void*)SKIBOOT_OFFSET+sb.st_size); + + gcov_list_addr = strtoll(argv[2], NULL, 0); + + printf("Skiboot gcov_info_list at %p\n", (void*)gcov_list_addr); + + do { + gcov_info_list = (struct gcov_info *)(addr + (gcov_list_addr - SKIBOOT_OFFSET)); + write_gcda(addr, gcov_info_list); + gcov_list_addr = be64toh((u64)gcov_info_list->next); + + } while(gcov_list_addr); + + munmap(addr, sb.st_size); + close(fd); + + return 0; +} |