aboutsummaryrefslogtreecommitdiff
path: root/extract-gcov.c
diff options
context:
space:
mode:
authorStewart Smith <stewart@linux.vnet.ibm.com>2015-05-07 17:11:46 +1000
committerStewart Smith <stewart@linux.vnet.ibm.com>2015-05-15 07:59:18 +1000
commitd3cedfba5553227dec328ed0000221ed5c4294ad (patch)
tree70be07441f6b139779792e95588c59c43712468a /extract-gcov.c
parent614aa6a7833839146de2769a09de355a49b8ed3d (diff)
downloadskiboot-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.c220
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;
+}