mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-04 02:18:37 +00:00
To detect calls and returns, Callgrind's heuristic starts with using the jumpkind got from VEX for a control flow change instruction. However, for side exits, it always assumed a (conditional) jump, which holds true for x86, but e.g. not for ARM. This fixes Callgrind to use the jumpkind found by VEX for all exits, which should help making Callgrind work for ARM. It also moves the check whether a boring jump is actually a fall-through to instrumentation time. This changes (fixes) the result for indirect jumps to the next instruction, which should not be classified as fall-through (anyway, this case is probably very rare). This patch introduces an own enum for jump kinds in Callgrind. This is less confusing than misusing the VEX jump kind type, as Callgrinds wants to distinguish BB fall-throughs from real jumps (which both are Ijk_Boring in VEX). Also, setup_bbcc now stores separately whether the jump kind is conditional or not. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@12269
1749 lines
46 KiB
C
1749 lines
46 KiB
C
/*--------------------------------------------------------------------*/
|
|
/*--- Callgrind ---*/
|
|
/*--- dump.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of Callgrind, a Valgrind tool for call tracing.
|
|
|
|
Copyright (C) 2002-2011, Josef Weidendorfer (Josef.Weidendorfer@gmx.de)
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
|
|
The GNU General Public License is contained in the file COPYING.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "global.h"
|
|
|
|
#include "pub_tool_threadstate.h"
|
|
#include "pub_tool_libcfile.h"
|
|
|
|
|
|
/* Dump Part Counter */
|
|
static Int out_counter = 0;
|
|
|
|
static Char* out_file = 0;
|
|
static Char* out_directory = 0;
|
|
static Bool dumps_initialized = False;
|
|
|
|
/* Command */
|
|
static Char cmdbuf[BUF_LEN];
|
|
|
|
/* Total reads/writes/misses sum over all dumps and threads.
|
|
* Updated during CC traversal at dump time.
|
|
*/
|
|
FullCost CLG_(total_cost) = 0;
|
|
static FullCost dump_total_cost = 0;
|
|
|
|
EventMapping* CLG_(dumpmap) = 0;
|
|
|
|
/* Temporary output buffer for
|
|
* print_fn_pos, fprint_apos, fprint_fcost, fprint_jcc,
|
|
* fprint_fcc_ln, dump_run_info, dump_state_info
|
|
*/
|
|
static Char outbuf[FILENAME_LEN + FN_NAME_LEN + OBJ_NAME_LEN + COSTS_LEN];
|
|
|
|
Int CLG_(get_dump_counter)(void)
|
|
{
|
|
return out_counter;
|
|
}
|
|
|
|
Char* CLG_(get_out_file)()
|
|
{
|
|
CLG_(init_dumps)();
|
|
return out_file;
|
|
}
|
|
|
|
Char* CLG_(get_out_directory)()
|
|
{
|
|
CLG_(init_dumps)();
|
|
return out_directory;
|
|
}
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Output file related stuff ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* Boolean dumping array */
|
|
static Bool* dump_array = 0;
|
|
static Int dump_array_size = 0;
|
|
static Bool* obj_dumped = 0;
|
|
static Bool* file_dumped = 0;
|
|
static Bool* fn_dumped = 0;
|
|
static Bool* cxt_dumped = 0;
|
|
|
|
static
|
|
void reset_dump_array(void)
|
|
{
|
|
int i;
|
|
|
|
CLG_ASSERT(dump_array != 0);
|
|
|
|
for(i=0;i<dump_array_size;i++)
|
|
dump_array[i] = False;
|
|
}
|
|
|
|
static
|
|
void init_dump_array(void)
|
|
{
|
|
dump_array_size = CLG_(stat).distinct_objs +
|
|
CLG_(stat).distinct_files +
|
|
CLG_(stat).distinct_fns +
|
|
CLG_(stat).context_counter;
|
|
CLG_ASSERT(dump_array == 0);
|
|
dump_array = (Bool*) CLG_MALLOC("cl.dump.ida.1",
|
|
dump_array_size * sizeof(Bool));
|
|
obj_dumped = dump_array;
|
|
file_dumped = obj_dumped + CLG_(stat).distinct_objs;
|
|
fn_dumped = file_dumped + CLG_(stat).distinct_files;
|
|
cxt_dumped = fn_dumped + CLG_(stat).distinct_fns;
|
|
|
|
reset_dump_array();
|
|
|
|
CLG_DEBUG(1, " init_dump_array: size %d\n", dump_array_size);
|
|
}
|
|
|
|
static __inline__
|
|
void free_dump_array(void)
|
|
{
|
|
CLG_ASSERT(dump_array != 0);
|
|
VG_(free)(dump_array);
|
|
|
|
dump_array = 0;
|
|
obj_dumped = 0;
|
|
file_dumped = 0;
|
|
fn_dumped = 0;
|
|
cxt_dumped = 0;
|
|
}
|
|
|
|
|
|
/* Initialize to an invalid position */
|
|
static __inline__
|
|
void init_fpos(FnPos* p)
|
|
{
|
|
p->file = 0;
|
|
p->fn = 0;
|
|
p->obj = 0;
|
|
p->cxt = 0;
|
|
p->rec_index = 0;
|
|
}
|
|
|
|
|
|
#if 0
|
|
static __inline__
|
|
static void my_fwrite(Int fd, Char* buf, Int len)
|
|
{
|
|
VG_(write)(fd, (void*)buf, len);
|
|
}
|
|
#else
|
|
|
|
#define FWRITE_BUFSIZE 32000
|
|
#define FWRITE_THROUGH 10000
|
|
static Char fwrite_buf[FWRITE_BUFSIZE];
|
|
static Int fwrite_pos;
|
|
static Int fwrite_fd = -1;
|
|
|
|
static __inline__
|
|
void fwrite_flush(void)
|
|
{
|
|
if ((fwrite_fd>=0) && (fwrite_pos>0))
|
|
VG_(write)(fwrite_fd, (void*)fwrite_buf, fwrite_pos);
|
|
fwrite_pos = 0;
|
|
}
|
|
|
|
static void my_fwrite(Int fd, Char* buf, Int len)
|
|
{
|
|
if (fwrite_fd != fd) {
|
|
fwrite_flush();
|
|
fwrite_fd = fd;
|
|
}
|
|
if (len > FWRITE_THROUGH) {
|
|
fwrite_flush();
|
|
VG_(write)(fd, (void*)buf, len);
|
|
return;
|
|
}
|
|
if (FWRITE_BUFSIZE - fwrite_pos <= len) fwrite_flush();
|
|
VG_(strncpy)(fwrite_buf + fwrite_pos, buf, len);
|
|
fwrite_pos += len;
|
|
}
|
|
#endif
|
|
|
|
|
|
static void print_obj(Char* buf, obj_node* obj)
|
|
{
|
|
//int n;
|
|
|
|
if (CLG_(clo).compress_strings) {
|
|
CLG_ASSERT(obj_dumped != 0);
|
|
if (obj_dumped[obj->number])
|
|
/*n =*/ VG_(sprintf)(buf, "(%d)\n", obj->number);
|
|
else {
|
|
/*n =*/ VG_(sprintf)(buf, "(%d) %s\n",
|
|
obj->number, obj->name);
|
|
}
|
|
}
|
|
else
|
|
/*n =*/ VG_(sprintf)(buf, "%s\n", obj->name);
|
|
|
|
#if 0
|
|
/* add mapping parameters the first time a object is dumped
|
|
* format: mp=0xSTART SIZE 0xOFFSET */
|
|
if (!obj_dumped[obj->number]) {
|
|
obj_dumped[obj->number];
|
|
VG_(sprintf)(buf+n, "mp=%p %p %p\n",
|
|
pos->obj->start, pos->obj->size, pos->obj->offset);
|
|
}
|
|
#else
|
|
obj_dumped[obj->number] = True;
|
|
#endif
|
|
}
|
|
|
|
static void print_file(Char* buf, file_node* file)
|
|
{
|
|
if (CLG_(clo).compress_strings) {
|
|
CLG_ASSERT(file_dumped != 0);
|
|
if (file_dumped[file->number])
|
|
VG_(sprintf)(buf, "(%d)\n", file->number);
|
|
else {
|
|
VG_(sprintf)(buf, "(%d) %s\n",
|
|
file->number, file->name);
|
|
file_dumped[file->number] = True;
|
|
}
|
|
}
|
|
else
|
|
VG_(sprintf)(buf, "%s\n", file->name);
|
|
}
|
|
|
|
/*
|
|
* tag can be "fn", "cfn", "jfn"
|
|
*/
|
|
static void print_fn(Int fd, Char* buf, Char* tag, fn_node* fn)
|
|
{
|
|
int p;
|
|
p = VG_(sprintf)(buf, "%s=",tag);
|
|
if (CLG_(clo).compress_strings) {
|
|
CLG_ASSERT(fn_dumped != 0);
|
|
if (fn_dumped[fn->number])
|
|
p += VG_(sprintf)(buf+p, "(%d)\n", fn->number);
|
|
else {
|
|
p += VG_(sprintf)(buf+p, "(%d) %s\n",
|
|
fn->number, fn->name);
|
|
fn_dumped[fn->number] = True;
|
|
}
|
|
}
|
|
else
|
|
p += VG_(sprintf)(buf+p, "%s\n", fn->name);
|
|
|
|
my_fwrite(fd, buf, p);
|
|
}
|
|
|
|
static void print_mangled_fn(Int fd, Char* buf, Char* tag,
|
|
Context* cxt, int rec_index)
|
|
{
|
|
int p, i;
|
|
|
|
if (CLG_(clo).compress_strings && CLG_(clo).compress_mangled) {
|
|
|
|
int n;
|
|
Context* last;
|
|
|
|
CLG_ASSERT(cxt_dumped != 0);
|
|
if (cxt_dumped[cxt->base_number+rec_index]) {
|
|
p = VG_(sprintf)(buf, "%s=(%d)\n",
|
|
tag, cxt->base_number + rec_index);
|
|
my_fwrite(fd, buf, p);
|
|
return;
|
|
}
|
|
|
|
last = 0;
|
|
/* make sure that for all context parts compressed data is written */
|
|
for(i=cxt->size;i>0;i--) {
|
|
CLG_ASSERT(cxt->fn[i-1]->pure_cxt != 0);
|
|
n = cxt->fn[i-1]->pure_cxt->base_number;
|
|
if (cxt_dumped[n]) continue;
|
|
p = VG_(sprintf)(buf, "%s=(%d) %s\n",
|
|
tag, n, cxt->fn[i-1]->name);
|
|
my_fwrite(fd, buf, p);
|
|
|
|
cxt_dumped[n] = True;
|
|
last = cxt->fn[i-1]->pure_cxt;
|
|
}
|
|
/* If the last context was the context to print, we are finished */
|
|
if ((last == cxt) && (rec_index == 0)) return;
|
|
|
|
p = VG_(sprintf)(buf, "%s=(%d) (%d)", tag,
|
|
cxt->base_number + rec_index,
|
|
cxt->fn[0]->pure_cxt->base_number);
|
|
if (rec_index >0)
|
|
p += VG_(sprintf)(buf+p, "'%d", rec_index +1);
|
|
for(i=1;i<cxt->size;i++)
|
|
p += VG_(sprintf)(buf+p, "'(%d)",
|
|
cxt->fn[i]->pure_cxt->base_number);
|
|
p += VG_(sprintf)(buf+p, "\n");
|
|
my_fwrite(fd, buf, p);
|
|
|
|
cxt_dumped[cxt->base_number+rec_index] = True;
|
|
return;
|
|
}
|
|
|
|
|
|
p = VG_(sprintf)(buf, "%s=", tag);
|
|
if (CLG_(clo).compress_strings) {
|
|
CLG_ASSERT(cxt_dumped != 0);
|
|
if (cxt_dumped[cxt->base_number+rec_index]) {
|
|
p += VG_(sprintf)(buf+p, "(%d)\n", cxt->base_number + rec_index);
|
|
my_fwrite(fd, buf, p);
|
|
return;
|
|
}
|
|
else {
|
|
p += VG_(sprintf)(buf+p, "(%d) ", cxt->base_number + rec_index);
|
|
cxt_dumped[cxt->base_number+rec_index] = True;
|
|
}
|
|
}
|
|
|
|
p += VG_(sprintf)(buf+p, "%s", cxt->fn[0]->name);
|
|
if (rec_index >0)
|
|
p += VG_(sprintf)(buf+p, "'%d", rec_index +1);
|
|
for(i=1;i<cxt->size;i++)
|
|
p += VG_(sprintf)(buf+p, "'%s", cxt->fn[i]->name);
|
|
|
|
p += VG_(sprintf)(buf+p, "\n");
|
|
my_fwrite(fd, buf, p);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Print function position of the BBCC, but only print info differing to
|
|
* the <last> position, update <last>
|
|
* Return True if something changes.
|
|
*/
|
|
static Bool print_fn_pos(int fd, FnPos* last, BBCC* bbcc)
|
|
{
|
|
Bool res = False;
|
|
|
|
CLG_ASSERT(bbcc && bbcc->cxt);
|
|
|
|
CLG_DEBUGIF(3) {
|
|
CLG_DEBUG(2, "+ print_fn_pos: ");
|
|
CLG_(print_cxt)(16, bbcc->cxt, bbcc->rec_index);
|
|
}
|
|
|
|
if (!CLG_(clo).mangle_names) {
|
|
if (last->rec_index != bbcc->rec_index) {
|
|
VG_(sprintf)(outbuf, "rec=%d\n\n", bbcc->rec_index);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
last->rec_index = bbcc->rec_index;
|
|
last->cxt = 0; /* reprint context */
|
|
res = True;
|
|
}
|
|
|
|
if (last->cxt != bbcc->cxt) {
|
|
fn_node* last_from = (last->cxt && last->cxt->size >1) ?
|
|
last->cxt->fn[1] : 0;
|
|
fn_node* curr_from = (bbcc->cxt->size >1) ?
|
|
bbcc->cxt->fn[1] : 0;
|
|
if (curr_from == 0) {
|
|
if (last_from != 0) {
|
|
/* switch back to no context */
|
|
VG_(sprintf)(outbuf, "frfn=(spontaneous)\n");
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
res = True;
|
|
}
|
|
}
|
|
else if (last_from != curr_from) {
|
|
print_fn(fd,outbuf,"frfn", curr_from);
|
|
res = True;
|
|
}
|
|
last->cxt = bbcc->cxt;
|
|
}
|
|
}
|
|
|
|
if (last->obj != bbcc->cxt->fn[0]->file->obj) {
|
|
VG_(sprintf)(outbuf, "ob=");
|
|
print_obj(outbuf+3, bbcc->cxt->fn[0]->file->obj);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
last->obj = bbcc->cxt->fn[0]->file->obj;
|
|
res = True;
|
|
}
|
|
|
|
if (last->file != bbcc->cxt->fn[0]->file) {
|
|
VG_(sprintf)(outbuf, "fl=");
|
|
print_file(outbuf+3, bbcc->cxt->fn[0]->file);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
last->file = bbcc->cxt->fn[0]->file;
|
|
res = True;
|
|
}
|
|
|
|
if (!CLG_(clo).mangle_names) {
|
|
if (last->fn != bbcc->cxt->fn[0]) {
|
|
print_fn(fd,outbuf, "fn", bbcc->cxt->fn[0]);
|
|
last->fn = bbcc->cxt->fn[0];
|
|
res = True;
|
|
}
|
|
}
|
|
else {
|
|
/* Print mangled name if context or rec_index changes */
|
|
if ((last->rec_index != bbcc->rec_index) ||
|
|
(last->cxt != bbcc->cxt)) {
|
|
|
|
print_mangled_fn(fd, outbuf, "fn", bbcc->cxt, bbcc->rec_index);
|
|
last->fn = bbcc->cxt->fn[0];
|
|
last->rec_index = bbcc->rec_index;
|
|
res = True;
|
|
}
|
|
}
|
|
|
|
last->cxt = bbcc->cxt;
|
|
|
|
CLG_DEBUG(2, "- print_fn_pos: %s\n", res ? "changed" : "");
|
|
|
|
return res;
|
|
}
|
|
|
|
/* the debug lookup cache is useful if BBCC for same BB are
|
|
* dumped directly in a row. This is a direct mapped cache.
|
|
*/
|
|
#define DEBUG_CACHE_SIZE 1777
|
|
|
|
static Addr debug_cache_addr[DEBUG_CACHE_SIZE];
|
|
static file_node* debug_cache_file[DEBUG_CACHE_SIZE];
|
|
static int debug_cache_line[DEBUG_CACHE_SIZE];
|
|
static Bool debug_cache_info[DEBUG_CACHE_SIZE];
|
|
|
|
static __inline__
|
|
void init_debug_cache(void)
|
|
{
|
|
int i;
|
|
for(i=0;i<DEBUG_CACHE_SIZE;i++) {
|
|
debug_cache_addr[i] = 0;
|
|
debug_cache_file[i] = 0;
|
|
debug_cache_line[i] = 0;
|
|
debug_cache_info[i] = 0;
|
|
}
|
|
}
|
|
|
|
static /* __inline__ */
|
|
Bool get_debug_pos(BBCC* bbcc, Addr addr, AddrPos* p)
|
|
{
|
|
Char file[FILENAME_LEN];
|
|
Char dir[FILENAME_LEN];
|
|
Bool found_file_line, found_dirname;
|
|
|
|
int cachepos = addr % DEBUG_CACHE_SIZE;
|
|
|
|
if (debug_cache_addr[cachepos] == addr) {
|
|
p->line = debug_cache_line[cachepos];
|
|
p->file = debug_cache_file[cachepos];
|
|
found_file_line = debug_cache_info[cachepos];
|
|
}
|
|
else {
|
|
found_file_line = VG_(get_filename_linenum)(addr,
|
|
file, FILENAME_LEN,
|
|
dir, FILENAME_LEN,
|
|
&found_dirname,
|
|
&(p->line));
|
|
if (!found_file_line) {
|
|
VG_(strcpy)(file, "???");
|
|
p->line = 0;
|
|
}
|
|
if (found_dirname) {
|
|
// +1 for the '/'.
|
|
CLG_ASSERT(VG_(strlen)(dir) + VG_(strlen)(file) + 1 < FILENAME_LEN);
|
|
VG_(strcat)(dir, "/"); // Append '/'
|
|
VG_(strcat)(dir, file); // Append file to dir
|
|
VG_(strcpy)(file, dir); // Move dir+file to file
|
|
}
|
|
p->file = CLG_(get_file_node)(bbcc->bb->obj, file);
|
|
|
|
debug_cache_info[cachepos] = found_file_line;
|
|
debug_cache_addr[cachepos] = addr;
|
|
debug_cache_line[cachepos] = p->line;
|
|
debug_cache_file[cachepos] = p->file;
|
|
}
|
|
|
|
/* Address offset from bbcc start address */
|
|
p->addr = addr - bbcc->bb->obj->offset;
|
|
p->bb_addr = bbcc->bb->offset;
|
|
|
|
CLG_DEBUG(3, " get_debug_pos(%#lx): BB %#lx, fn '%s', file '%s', line %u\n",
|
|
addr, bb_addr(bbcc->bb), bbcc->cxt->fn[0]->name,
|
|
p->file->name, p->line);
|
|
|
|
return found_file_line;
|
|
}
|
|
|
|
|
|
/* copy file position and init cost */
|
|
static void init_apos(AddrPos* p, Addr addr, Addr bbaddr, file_node* file)
|
|
{
|
|
p->addr = addr;
|
|
p->bb_addr = bbaddr;
|
|
p->file = file;
|
|
p->line = 0;
|
|
}
|
|
|
|
static void copy_apos(AddrPos* dst, AddrPos* src)
|
|
{
|
|
dst->addr = src->addr;
|
|
dst->bb_addr = src->bb_addr;
|
|
dst->file = src->file;
|
|
dst->line = src->line;
|
|
}
|
|
|
|
/* copy file position and init cost */
|
|
static void init_fcost(AddrCost* c, Addr addr, Addr bbaddr, file_node* file)
|
|
{
|
|
init_apos( &(c->p), addr, bbaddr, file);
|
|
/* FIXME: This is a memory leak as a AddrCost is inited multiple times */
|
|
c->cost = CLG_(get_eventset_cost)( CLG_(sets).full );
|
|
CLG_(init_cost)( CLG_(sets).full, c->cost );
|
|
}
|
|
|
|
|
|
/**
|
|
* print position change inside of a BB (last -> curr)
|
|
* this doesn't update last to curr!
|
|
*/
|
|
static void fprint_apos(Int fd, AddrPos* curr, AddrPos* last, file_node* func_file)
|
|
{
|
|
CLG_ASSERT(curr->file != 0);
|
|
CLG_DEBUG(2, " print_apos(file '%s', line %d, bb %#lx, addr %#lx) fnFile '%s'\n",
|
|
curr->file->name, curr->line, curr->bb_addr, curr->addr,
|
|
func_file->name);
|
|
|
|
if (curr->file != last->file) {
|
|
|
|
/* if we switch back to orig file, use fe=... */
|
|
if (curr->file == func_file)
|
|
VG_(sprintf)(outbuf, "fe=");
|
|
else
|
|
VG_(sprintf)(outbuf, "fi=");
|
|
print_file(outbuf+3, curr->file);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
|
|
if (CLG_(clo).dump_bbs) {
|
|
if (curr->line != last->line) {
|
|
VG_(sprintf)(outbuf, "ln=%d\n", curr->line);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Print a position.
|
|
* This prints out differences if allowed
|
|
*
|
|
* This doesn't set last to curr afterwards!
|
|
*/
|
|
static
|
|
void fprint_pos(Int fd, AddrPos* curr, AddrPos* last)
|
|
{
|
|
if (0) //CLG_(clo).dump_bbs)
|
|
VG_(sprintf)(outbuf, "%lu ", curr->addr - curr->bb_addr);
|
|
else {
|
|
int p = 0;
|
|
if (CLG_(clo).dump_instr) {
|
|
int diff = curr->addr - last->addr;
|
|
if ( CLG_(clo).compress_pos && (last->addr >0) &&
|
|
(diff > -100) && (diff < 100)) {
|
|
if (diff >0)
|
|
p = VG_(sprintf)(outbuf, "+%d ", diff);
|
|
else if (diff==0)
|
|
p = VG_(sprintf)(outbuf, "* ");
|
|
else
|
|
p = VG_(sprintf)(outbuf, "%d ", diff);
|
|
}
|
|
else
|
|
p = VG_(sprintf)(outbuf, "%#lx ", curr->addr);
|
|
}
|
|
|
|
if (CLG_(clo).dump_bb) {
|
|
int diff = curr->bb_addr - last->bb_addr;
|
|
if ( CLG_(clo).compress_pos && (last->bb_addr >0) &&
|
|
(diff > -100) && (diff < 100)) {
|
|
if (diff >0)
|
|
p += VG_(sprintf)(outbuf+p, "+%d ", diff);
|
|
else if (diff==0)
|
|
p += VG_(sprintf)(outbuf+p, "* ");
|
|
else
|
|
p += VG_(sprintf)(outbuf+p, "%d ", diff);
|
|
}
|
|
else
|
|
p += VG_(sprintf)(outbuf+p, "%#lx ", curr->bb_addr);
|
|
}
|
|
|
|
if (CLG_(clo).dump_line) {
|
|
int diff = curr->line - last->line;
|
|
if ( CLG_(clo).compress_pos && (last->line >0) &&
|
|
(diff > -100) && (diff < 100)) {
|
|
|
|
if (diff >0)
|
|
VG_(sprintf)(outbuf+p, "+%d ", diff);
|
|
else if (diff==0)
|
|
VG_(sprintf)(outbuf+p, "* ");
|
|
else
|
|
VG_(sprintf)(outbuf+p, "%d ", diff);
|
|
}
|
|
else
|
|
VG_(sprintf)(outbuf+p, "%u ", curr->line);
|
|
}
|
|
}
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
|
|
|
|
/**
|
|
* Print events.
|
|
*/
|
|
|
|
static
|
|
void fprint_cost(int fd, EventMapping* es, ULong* cost)
|
|
{
|
|
int p = CLG_(sprint_mappingcost)(outbuf, es, cost);
|
|
VG_(sprintf)(outbuf+p, "\n");
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* Write the cost of a source line; only that parts of the source
|
|
* position are written that changed relative to last written position.
|
|
* funcPos is the source position of the first line of actual function.
|
|
* Something is written only if cost != 0; returns True in this case.
|
|
*/
|
|
static void fprint_fcost(Int fd, AddrCost* c, AddrPos* last)
|
|
{
|
|
CLG_DEBUGIF(3) {
|
|
CLG_DEBUG(2, " print_fcost(file '%s', line %d, bb %#lx, addr %#lx):\n",
|
|
c->p.file->name, c->p.line, c->p.bb_addr, c->p.addr);
|
|
CLG_(print_cost)(-5, CLG_(sets).full, c->cost);
|
|
}
|
|
|
|
fprint_pos(fd, &(c->p), last);
|
|
copy_apos( last, &(c->p) ); /* update last to current position */
|
|
|
|
fprint_cost(fd, CLG_(dumpmap), c->cost);
|
|
|
|
/* add cost to total */
|
|
CLG_(add_and_zero_cost)( CLG_(sets).full, dump_total_cost, c->cost );
|
|
}
|
|
|
|
|
|
/* Write out the calls from jcc (at pos)
|
|
*/
|
|
static void fprint_jcc(Int fd, jCC* jcc, AddrPos* curr, AddrPos* last, ULong ecounter)
|
|
{
|
|
static AddrPos target;
|
|
file_node* file;
|
|
obj_node* obj;
|
|
|
|
CLG_DEBUGIF(2) {
|
|
CLG_DEBUG(2, " fprint_jcc (jkind %d)\n", jcc->jmpkind);
|
|
CLG_(print_jcc)(-10, jcc);
|
|
}
|
|
|
|
CLG_ASSERT(jcc->to !=0);
|
|
CLG_ASSERT(jcc->from !=0);
|
|
|
|
if (!get_debug_pos(jcc->to, bb_addr(jcc->to->bb), &target)) {
|
|
/* if we don't have debug info, don't switch to file "???" */
|
|
target.file = last->file;
|
|
}
|
|
|
|
if ((jcc->jmpkind == jk_CondJump) || (jcc->jmpkind == jk_Jump)) {
|
|
|
|
/* this is a JCC for a followed conditional or boring jump. */
|
|
CLG_ASSERT(CLG_(is_zero_cost)( CLG_(sets).full, jcc->cost));
|
|
|
|
/* objects among jumps should be the same.
|
|
* Otherwise this jump would have been changed to a call
|
|
* (see setup_bbcc)
|
|
*/
|
|
CLG_ASSERT(jcc->from->bb->obj == jcc->to->bb->obj);
|
|
|
|
/* only print if target position info is usefull */
|
|
if (!CLG_(clo).dump_instr && !CLG_(clo).dump_bb && target.line==0) {
|
|
jcc->call_counter = 0;
|
|
return;
|
|
}
|
|
|
|
/* Different files/functions are possible e.g. with longjmp's
|
|
* which change the stack, and thus context
|
|
*/
|
|
if (last->file != target.file) {
|
|
VG_(sprintf)(outbuf, "jfi=");
|
|
print_file(outbuf+4, target.file);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
|
|
if (jcc->from->cxt != jcc->to->cxt) {
|
|
if (CLG_(clo).mangle_names)
|
|
print_mangled_fn(fd, outbuf, "jfn",
|
|
jcc->to->cxt, jcc->to->rec_index);
|
|
else
|
|
print_fn(fd, outbuf, "jfn", jcc->to->cxt->fn[0]);
|
|
}
|
|
|
|
if (jcc->jmpkind == jk_CondJump) {
|
|
/* format: jcnd=<followed>/<executions> <target> */
|
|
VG_(sprintf)(outbuf, "jcnd=%llu/%llu ",
|
|
jcc->call_counter, ecounter);
|
|
}
|
|
else {
|
|
/* format: jump=<jump count> <target> */
|
|
VG_(sprintf)(outbuf, "jump=%llu ",
|
|
jcc->call_counter);
|
|
}
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
|
|
fprint_pos(fd, &target, last);
|
|
my_fwrite(fd, "\n", 1);
|
|
fprint_pos(fd, curr, last);
|
|
my_fwrite(fd, "\n", 1);
|
|
|
|
jcc->call_counter = 0;
|
|
return;
|
|
}
|
|
|
|
file = jcc->to->cxt->fn[0]->file;
|
|
obj = jcc->to->bb->obj;
|
|
|
|
/* object of called position different to object of this function?*/
|
|
if (jcc->from->cxt->fn[0]->file->obj != obj) {
|
|
VG_(sprintf)(outbuf, "cob=");
|
|
print_obj(outbuf+4, obj);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
|
|
/* file of called position different to current file? */
|
|
if (last->file != file) {
|
|
VG_(sprintf)(outbuf, "cfi=");
|
|
print_file(outbuf+4, file);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
|
|
if (CLG_(clo).mangle_names)
|
|
print_mangled_fn(fd, outbuf, "cfn", jcc->to->cxt, jcc->to->rec_index);
|
|
else
|
|
print_fn(fd, outbuf, "cfn", jcc->to->cxt->fn[0]);
|
|
|
|
if (!CLG_(is_zero_cost)( CLG_(sets).full, jcc->cost)) {
|
|
VG_(sprintf)(outbuf, "calls=%llu ",
|
|
jcc->call_counter);
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
|
|
fprint_pos(fd, &target, last);
|
|
my_fwrite(fd, "\n", 1);
|
|
fprint_pos(fd, curr, last);
|
|
fprint_cost(fd, CLG_(dumpmap), jcc->cost);
|
|
|
|
CLG_(init_cost)( CLG_(sets).full, jcc->cost );
|
|
|
|
jcc->call_counter = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Cost summation of functions.We use alternately ccSum[0/1], thus
|
|
* ssSum[currSum] for recently read lines with same line number.
|
|
*/
|
|
static AddrCost ccSum[2];
|
|
static int currSum;
|
|
|
|
/*
|
|
* Print all costs of a BBCC:
|
|
* - FCCs of instructions
|
|
* - JCCs of the unique jump of this BB
|
|
* returns True if something was written
|
|
*/
|
|
static Bool fprint_bbcc(Int fd, BBCC* bbcc, AddrPos* last)
|
|
{
|
|
InstrInfo* instr_info;
|
|
ULong ecounter;
|
|
Bool something_written = False;
|
|
jCC* jcc;
|
|
AddrCost *currCost, *newCost;
|
|
Int jcc_count = 0, instr, i, jmp;
|
|
BB* bb = bbcc->bb;
|
|
|
|
CLG_ASSERT(bbcc->cxt != 0);
|
|
CLG_DEBUGIF(1) {
|
|
VG_(printf)("+ fprint_bbcc (Instr %d): ", bb->instr_count);
|
|
CLG_(print_bbcc)(15, bbcc);
|
|
}
|
|
|
|
CLG_ASSERT(currSum == 0 || currSum == 1);
|
|
currCost = &(ccSum[currSum]);
|
|
newCost = &(ccSum[1-currSum]);
|
|
|
|
ecounter = bbcc->ecounter_sum;
|
|
jmp = 0;
|
|
instr_info = &(bb->instr[0]);
|
|
for(instr=0; instr<bb->instr_count; instr++, instr_info++) {
|
|
|
|
/* get debug info of current instruction address and dump cost
|
|
* if CLG_(clo).dump_bbs or file/line has changed
|
|
*/
|
|
if (!get_debug_pos(bbcc, bb_addr(bb) + instr_info->instr_offset,
|
|
&(newCost->p))) {
|
|
/* if we don't have debug info, don't switch to file "???" */
|
|
newCost->p.file = bbcc->cxt->fn[0]->file;
|
|
}
|
|
|
|
if (CLG_(clo).dump_bbs || CLG_(clo).dump_instr ||
|
|
(newCost->p.line != currCost->p.line) ||
|
|
(newCost->p.file != currCost->p.file)) {
|
|
|
|
if (!CLG_(is_zero_cost)( CLG_(sets).full, currCost->cost )) {
|
|
something_written = True;
|
|
|
|
fprint_apos(fd, &(currCost->p), last, bbcc->cxt->fn[0]->file);
|
|
fprint_fcost(fd, currCost, last);
|
|
}
|
|
|
|
/* switch buffers */
|
|
currSum = 1 - currSum;
|
|
currCost = &(ccSum[currSum]);
|
|
newCost = &(ccSum[1-currSum]);
|
|
}
|
|
|
|
/* add line cost to current cost sum */
|
|
(*CLG_(cachesim).add_icost)(currCost->cost, bbcc, instr_info, ecounter);
|
|
|
|
/* print jcc's if there are: only jumps */
|
|
if (bb->jmp[jmp].instr == instr) {
|
|
jcc_count=0;
|
|
for(jcc=bbcc->jmp[jmp].jcc_list; jcc; jcc=jcc->next_from)
|
|
if (((jcc->jmpkind != jk_Call) && (jcc->call_counter >0)) ||
|
|
(!CLG_(is_zero_cost)( CLG_(sets).full, jcc->cost )))
|
|
jcc_count++;
|
|
|
|
if (jcc_count>0) {
|
|
if (!CLG_(is_zero_cost)( CLG_(sets).full, currCost->cost )) {
|
|
/* no need to switch buffers, as position is the same */
|
|
fprint_apos(fd, &(currCost->p), last, bbcc->cxt->fn[0]->file);
|
|
fprint_fcost(fd, currCost, last);
|
|
}
|
|
get_debug_pos(bbcc, bb_addr(bb)+instr_info->instr_offset, &(currCost->p));
|
|
fprint_apos(fd, &(currCost->p), last, bbcc->cxt->fn[0]->file);
|
|
something_written = True;
|
|
for(jcc=bbcc->jmp[jmp].jcc_list; jcc; jcc=jcc->next_from) {
|
|
if (((jcc->jmpkind != jk_Call) && (jcc->call_counter >0)) ||
|
|
(!CLG_(is_zero_cost)( CLG_(sets).full, jcc->cost )))
|
|
fprint_jcc(fd, jcc, &(currCost->p), last, ecounter);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update execution counter */
|
|
if (jmp < bb->cjmp_count)
|
|
if (bb->jmp[jmp].instr == instr) {
|
|
ecounter -= bbcc->jmp[jmp].ecounter;
|
|
jmp++;
|
|
}
|
|
}
|
|
|
|
/* jCCs at end? If yes, dump cumulated line info first */
|
|
jcc_count = 0;
|
|
for(jcc=bbcc->jmp[jmp].jcc_list; jcc; jcc=jcc->next_from) {
|
|
/* yes, if JCC only counts jmp arcs or cost >0 */
|
|
if ( ((jcc->jmpkind != jk_Call) && (jcc->call_counter >0)) ||
|
|
(!CLG_(is_zero_cost)( CLG_(sets).full, jcc->cost )))
|
|
jcc_count++;
|
|
}
|
|
|
|
if ( (bbcc->skipped &&
|
|
!CLG_(is_zero_cost)(CLG_(sets).full, bbcc->skipped)) ||
|
|
(jcc_count>0) ) {
|
|
|
|
if (!CLG_(is_zero_cost)( CLG_(sets).full, currCost->cost )) {
|
|
/* no need to switch buffers, as position is the same */
|
|
fprint_apos(fd, &(currCost->p), last, bbcc->cxt->fn[0]->file);
|
|
fprint_fcost(fd, currCost, last);
|
|
}
|
|
|
|
get_debug_pos(bbcc, bb_jmpaddr(bb), &(currCost->p));
|
|
fprint_apos(fd, &(currCost->p), last, bbcc->cxt->fn[0]->file);
|
|
something_written = True;
|
|
|
|
/* first, print skipped costs for calls */
|
|
if (bbcc->skipped && !CLG_(is_zero_cost)( CLG_(sets).full,
|
|
bbcc->skipped )) {
|
|
CLG_(add_and_zero_cost)( CLG_(sets).full,
|
|
currCost->cost, bbcc->skipped );
|
|
#if 0
|
|
VG_(sprintf)(outbuf, "# Skipped\n");
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
#endif
|
|
fprint_fcost(fd, currCost, last);
|
|
}
|
|
|
|
if (jcc_count > 0)
|
|
for(jcc=bbcc->jmp[jmp].jcc_list; jcc; jcc=jcc->next_from) {
|
|
CLG_ASSERT(jcc->jmp == jmp);
|
|
if ( ((jcc->jmpkind != jk_Call) && (jcc->call_counter >0)) ||
|
|
(!CLG_(is_zero_cost)( CLG_(sets).full, jcc->cost )))
|
|
|
|
fprint_jcc(fd, jcc, &(currCost->p), last, ecounter);
|
|
}
|
|
}
|
|
|
|
if (CLG_(clo).dump_bbs || CLG_(clo).dump_bb) {
|
|
if (!CLG_(is_zero_cost)( CLG_(sets).full, currCost->cost )) {
|
|
something_written = True;
|
|
|
|
fprint_apos(fd, &(currCost->p), last, bbcc->cxt->fn[0]->file);
|
|
fprint_fcost(fd, currCost, last);
|
|
}
|
|
if (CLG_(clo).dump_bbs) my_fwrite(fd, (void*)"\n", 1);
|
|
|
|
/* when every cost was immediatly written, we must have done so,
|
|
* as this function is only called when there's cost in a BBCC
|
|
*/
|
|
CLG_ASSERT(something_written);
|
|
}
|
|
|
|
bbcc->ecounter_sum = 0;
|
|
for(i=0; i<=bbcc->bb->cjmp_count; i++)
|
|
bbcc->jmp[i].ecounter = 0;
|
|
bbcc->ret_counter = 0;
|
|
|
|
CLG_DEBUG(1, "- fprint_bbcc: JCCs %d\n", jcc_count);
|
|
|
|
return something_written;
|
|
}
|
|
|
|
/* order by
|
|
* recursion,
|
|
* from->bb->obj, from->bb->fn
|
|
* obj, fn[0]->file, fn
|
|
* address
|
|
*/
|
|
static int my_cmp(BBCC** pbbcc1, BBCC** pbbcc2)
|
|
{
|
|
#if 0
|
|
return (*pbbcc1)->bb->offset - (*pbbcc2)->bb->offset;
|
|
#else
|
|
BBCC *bbcc1 = *pbbcc1;
|
|
BBCC *bbcc2 = *pbbcc2;
|
|
Context* cxt1 = bbcc1->cxt;
|
|
Context* cxt2 = bbcc2->cxt;
|
|
int off = 1;
|
|
|
|
if (cxt1->fn[0]->file->obj != cxt2->fn[0]->file->obj)
|
|
return cxt1->fn[0]->file->obj - cxt2->fn[0]->file->obj;
|
|
|
|
if (cxt1->fn[0]->file != cxt2->fn[0]->file)
|
|
return cxt1->fn[0]->file - cxt2->fn[0]->file;
|
|
|
|
if (cxt1->fn[0] != cxt2->fn[0])
|
|
return cxt1->fn[0] - cxt2->fn[0];
|
|
|
|
if (bbcc1->rec_index != bbcc2->rec_index)
|
|
return bbcc1->rec_index - bbcc2->rec_index;
|
|
|
|
while((off < cxt1->size) && (off < cxt2->size)) {
|
|
fn_node* ffn1 = cxt1->fn[off];
|
|
fn_node* ffn2 = cxt2->fn[off];
|
|
if (ffn1->file->obj != ffn2->file->obj)
|
|
return ffn1->file->obj - ffn2->file->obj;
|
|
if (ffn1 != ffn2)
|
|
return ffn1 - ffn2;
|
|
off++;
|
|
}
|
|
if (cxt1->size > cxt2->size) return 1;
|
|
else if (cxt1->size < cxt2->size) return -1;
|
|
|
|
return bbcc1->bb->offset - bbcc2->bb->offset;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* modified version of:
|
|
*
|
|
* qsort -- qsort interface implemented by faster quicksort.
|
|
* J. L. Bentley and M. D. McIlroy, SPE 23 (1993) 1249-1265.
|
|
* Copyright 1993, John Wiley.
|
|
*/
|
|
|
|
static __inline__
|
|
void swapfunc(BBCC** a, BBCC** b, int n)
|
|
{
|
|
while(n>0) {
|
|
BBCC* t = *a; *a = *b; *b = t;
|
|
a++, b++;
|
|
n--;
|
|
}
|
|
}
|
|
|
|
static __inline__
|
|
void swap(BBCC** a, BBCC** b)
|
|
{
|
|
BBCC* t;
|
|
t = *a; *a = *b; *b = t;
|
|
}
|
|
|
|
#define min(x, y) ((x)<=(y) ? (x) : (y))
|
|
|
|
static
|
|
BBCC** med3(BBCC **a, BBCC **b, BBCC **c, int (*cmp)(BBCC**,BBCC**))
|
|
{ return cmp(a, b) < 0 ?
|
|
(cmp(b, c) < 0 ? b : cmp(a, c) < 0 ? c : a)
|
|
: (cmp(b, c) > 0 ? b : cmp(a, c) > 0 ? c : a);
|
|
}
|
|
|
|
static BBCC** qsort_start = 0;
|
|
|
|
static void qsort(BBCC **a, int n, int (*cmp)(BBCC**,BBCC**))
|
|
{
|
|
BBCC **pa, **pb, **pc, **pd, **pl, **pm, **pn, **pv;
|
|
int s, r;
|
|
BBCC* v;
|
|
|
|
CLG_DEBUG(8, " qsort(%ld,%ld)\n", a-qsort_start + 0L, n + 0L);
|
|
|
|
if (n < 7) { /* Insertion sort on smallest arrays */
|
|
for (pm = a+1; pm < a+n; pm++)
|
|
for (pl = pm; pl > a && cmp(pl-1, pl) > 0; pl --)
|
|
swap(pl, pl-1);
|
|
|
|
CLG_DEBUGIF(8) {
|
|
for (pm = a; pm < a+n; pm++) {
|
|
VG_(printf)(" %3ld BB %#lx, ",
|
|
pm - qsort_start + 0L,
|
|
bb_addr((*pm)->bb));
|
|
CLG_(print_cxt)(9, (*pm)->cxt, (*pm)->rec_index);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
pm = a + n/2; /* Small arrays, middle element */
|
|
if (n > 7) {
|
|
pl = a;
|
|
pn = a + (n-1);
|
|
if (n > 40) { /* Big arrays, pseudomedian of 9 */
|
|
s = n/8;
|
|
pl = med3(pl, pl+s, pl+2*s, cmp);
|
|
pm = med3(pm-s, pm, pm+s, cmp);
|
|
pn = med3(pn-2*s, pn-s, pn, cmp);
|
|
}
|
|
pm = med3(pl, pm, pn, cmp); /* Mid-size, med of 3 */
|
|
}
|
|
|
|
|
|
v = *pm;
|
|
pv = &v;
|
|
pa = pb = a;
|
|
pc = pd = a + (n-1);
|
|
for (;;) {
|
|
while ((pb <= pc) && ((r=cmp(pb, pv)) <= 0)) {
|
|
if (r==0) {
|
|
/* same as pivot, to start */
|
|
swap(pa,pb); pa++;
|
|
}
|
|
pb ++;
|
|
}
|
|
while ((pb <= pc) && ((r=cmp(pc, pv)) >= 0)) {
|
|
if (r==0) {
|
|
/* same as pivot, to end */
|
|
swap(pc,pd); pd--;
|
|
}
|
|
pc --;
|
|
}
|
|
if (pb > pc) { break; }
|
|
swap(pb, pc);
|
|
pb ++;
|
|
pc --;
|
|
}
|
|
pb--;
|
|
pc++;
|
|
|
|
/* put pivot from start into middle */
|
|
if ((s = pa-a)>0) { for(r=0;r<s;r++) swap(a+r, pb+1-s+r); }
|
|
/* put pivot from end into middle */
|
|
if ((s = a+n-1-pd)>0) { for(r=0;r<s;r++) swap(pc+r, a+n-s+r); }
|
|
|
|
CLG_DEBUGIF(8) {
|
|
VG_(printf)(" PV BB %#lx, ", bb_addr((*pv)->bb));
|
|
CLG_(print_cxt)(9, (*pv)->cxt, (*pv)->rec_index);
|
|
|
|
s = pb-pa+1;
|
|
VG_(printf)(" Lower %ld - %ld:\n",
|
|
a-qsort_start + 0L,
|
|
a+s-1-qsort_start + 0L);
|
|
for (r=0;r<s;r++) {
|
|
pm = a+r;
|
|
VG_(printf)(" %3ld BB %#lx, ",
|
|
pm-qsort_start + 0L,
|
|
bb_addr((*pm)->bb));
|
|
CLG_(print_cxt)(9, (*pm)->cxt, (*pm)->rec_index);
|
|
}
|
|
|
|
s = pd-pc+1;
|
|
VG_(printf)(" Upper %ld - %ld:\n",
|
|
a+n-s-qsort_start + 0L,
|
|
a+n-1-qsort_start + 0L);
|
|
for (r=0;r<s;r++) {
|
|
pm = a+n-s+r;
|
|
VG_(printf)(" %3ld BB %#lx, ",
|
|
pm-qsort_start + 0L,
|
|
bb_addr((*pm)->bb));
|
|
CLG_(print_cxt)(9, (*pm)->cxt, (*pm)->rec_index);
|
|
}
|
|
}
|
|
|
|
if ((s = pb+1-pa) > 1) qsort(a, s, cmp);
|
|
if ((s = pd+1-pc) > 1) qsort(a+n-s, s, cmp);
|
|
}
|
|
|
|
|
|
/* Helpers for prepare_dump */
|
|
|
|
static Int prepare_count;
|
|
static BBCC** prepare_ptr;
|
|
|
|
|
|
static void hash_addCount(BBCC* bbcc)
|
|
{
|
|
if ((bbcc->ecounter_sum > 0) || (bbcc->ret_counter>0))
|
|
prepare_count++;
|
|
}
|
|
|
|
static void hash_addPtr(BBCC* bbcc)
|
|
{
|
|
if ((bbcc->ecounter_sum == 0) &&
|
|
(bbcc->ret_counter == 0)) return;
|
|
|
|
*prepare_ptr = bbcc;
|
|
prepare_ptr++;
|
|
}
|
|
|
|
|
|
static void cs_addCount(thread_info* ti)
|
|
{
|
|
Int i;
|
|
BBCC* bbcc;
|
|
|
|
/* add BBCCs with active call in call stack of current thread.
|
|
* update cost sums for active calls
|
|
*/
|
|
|
|
for(i = 0; i < CLG_(current_call_stack).sp; i++) {
|
|
call_entry* e = &(CLG_(current_call_stack).entry[i]);
|
|
if (e->jcc == 0) continue;
|
|
|
|
CLG_(add_diff_cost_lz)( CLG_(sets).full, &(e->jcc->cost),
|
|
e->enter_cost, CLG_(current_state).cost);
|
|
bbcc = e->jcc->from;
|
|
|
|
CLG_DEBUG(1, " [%2d] (tid %d), added active: %s\n",
|
|
i,CLG_(current_tid),bbcc->cxt->fn[0]->name);
|
|
|
|
if (bbcc->ecounter_sum>0 || bbcc->ret_counter>0) {
|
|
/* already counted */
|
|
continue;
|
|
}
|
|
prepare_count++;
|
|
}
|
|
}
|
|
|
|
static void cs_addPtr(thread_info* ti)
|
|
{
|
|
Int i;
|
|
BBCC* bbcc;
|
|
|
|
/* add BBCCs with active call in call stack of current thread.
|
|
* update cost sums for active calls
|
|
*/
|
|
|
|
for(i = 0; i < CLG_(current_call_stack).sp; i++) {
|
|
call_entry* e = &(CLG_(current_call_stack).entry[i]);
|
|
if (e->jcc == 0) continue;
|
|
|
|
bbcc = e->jcc->from;
|
|
|
|
if (bbcc->ecounter_sum>0 || bbcc->ret_counter>0) {
|
|
/* already counted */
|
|
continue;
|
|
}
|
|
|
|
*prepare_ptr = bbcc;
|
|
prepare_ptr++;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Put all BBCCs with costs into a sorted array.
|
|
* The returned arrays ends with a null pointer.
|
|
* Must be freed after dumping.
|
|
*/
|
|
static
|
|
BBCC** prepare_dump(void)
|
|
{
|
|
BBCC **array;
|
|
|
|
prepare_count = 0;
|
|
|
|
/* if we do not separate among threads, this gives all */
|
|
/* count number of BBCCs with >0 executions */
|
|
CLG_(forall_bbccs)(hash_addCount);
|
|
|
|
/* even if we do not separate among threads,
|
|
* call stacks are separated */
|
|
if (CLG_(clo).separate_threads)
|
|
cs_addCount(0);
|
|
else
|
|
CLG_(forall_threads)(cs_addCount);
|
|
|
|
CLG_DEBUG(0, "prepare_dump: %d BBCCs\n", prepare_count);
|
|
|
|
/* allocate bbcc array, insert BBCCs and sort */
|
|
prepare_ptr = array =
|
|
(BBCC**) CLG_MALLOC("cl.dump.pd.1",
|
|
(prepare_count+1) * sizeof(BBCC*));
|
|
|
|
CLG_(forall_bbccs)(hash_addPtr);
|
|
|
|
if (CLG_(clo).separate_threads)
|
|
cs_addPtr(0);
|
|
else
|
|
CLG_(forall_threads)(cs_addPtr);
|
|
|
|
CLG_ASSERT(array + prepare_count == prepare_ptr);
|
|
|
|
/* end mark */
|
|
*prepare_ptr = 0;
|
|
|
|
CLG_DEBUG(0," BBCCs inserted\n");
|
|
|
|
qsort_start = array;
|
|
qsort(array, prepare_count, my_cmp);
|
|
|
|
CLG_DEBUG(0," BBCCs sorted\n");
|
|
|
|
return array;
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fprint_cost_ln(int fd, Char* prefix,
|
|
EventMapping* em, ULong* cost)
|
|
{
|
|
int p;
|
|
|
|
p = VG_(sprintf)(outbuf, "%s", prefix);
|
|
p += CLG_(sprint_mappingcost)(outbuf + p, em, cost);
|
|
VG_(sprintf)(outbuf + p, "\n");
|
|
my_fwrite(fd, (void*)outbuf, VG_(strlen)(outbuf));
|
|
}
|
|
|
|
static ULong bbs_done = 0;
|
|
static Char* filename = 0;
|
|
|
|
static
|
|
void file_err(void)
|
|
{
|
|
VG_(message)(Vg_UserMsg,
|
|
"Error: can not open cache simulation output file `%s'\n",
|
|
filename );
|
|
VG_(exit)(1);
|
|
}
|
|
|
|
/**
|
|
* Create a new dump file and write header.
|
|
*
|
|
* Naming: <CLG_(clo).filename_base>.<pid>[.<part>][-<tid>]
|
|
* <part> is skipped for final dump (trigger==0)
|
|
* <tid> is skipped for thread 1 with CLG_(clo).separate_threads=no
|
|
*
|
|
* Returns the file descriptor, and -1 on error (no write permission)
|
|
*/
|
|
static int new_dumpfile(Char buf[BUF_LEN], int tid, Char* trigger)
|
|
{
|
|
Bool appending = False;
|
|
int i, fd;
|
|
FullCost sum = 0;
|
|
SysRes res;
|
|
|
|
CLG_ASSERT(dumps_initialized);
|
|
CLG_ASSERT(filename != 0);
|
|
|
|
if (!CLG_(clo).combine_dumps) {
|
|
i = VG_(sprintf)(filename, "%s", out_file);
|
|
|
|
if (trigger)
|
|
i += VG_(sprintf)(filename+i, ".%d", out_counter);
|
|
|
|
if (CLG_(clo).separate_threads)
|
|
VG_(sprintf)(filename+i, "-%02d", tid);
|
|
|
|
res = VG_(open)(filename, VKI_O_WRONLY|VKI_O_TRUNC, 0);
|
|
}
|
|
else {
|
|
VG_(sprintf)(filename, "%s", out_file);
|
|
res = VG_(open)(filename, VKI_O_WRONLY|VKI_O_APPEND, 0);
|
|
if (!sr_isError(res) && out_counter>1)
|
|
appending = True;
|
|
}
|
|
|
|
if (sr_isError(res)) {
|
|
res = VG_(open)(filename, VKI_O_CREAT|VKI_O_WRONLY,
|
|
VKI_S_IRUSR|VKI_S_IWUSR);
|
|
if (sr_isError(res)) {
|
|
/* If the file can not be opened for whatever reason (conflict
|
|
between multiple supervised processes?), give up now. */
|
|
file_err();
|
|
}
|
|
}
|
|
fd = (Int) sr_Res(res);
|
|
|
|
CLG_DEBUG(2, " new_dumpfile '%s'\n", filename);
|
|
|
|
if (!appending)
|
|
reset_dump_array();
|
|
|
|
|
|
if (!appending) {
|
|
/* version */
|
|
VG_(sprintf)(buf, "version: 1\n");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
|
|
/* creator */
|
|
VG_(sprintf)(buf, "creator: callgrind-" VERSION "\n");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
|
|
/* "pid:" line */
|
|
VG_(sprintf)(buf, "pid: %d\n", VG_(getpid)());
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
|
|
/* "cmd:" line */
|
|
VG_(strcpy)(buf, "cmd: ");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
my_fwrite(fd, (void*)cmdbuf, VG_(strlen)(cmdbuf));
|
|
}
|
|
|
|
VG_(sprintf)(buf, "\npart: %d\n", out_counter);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
if (CLG_(clo).separate_threads) {
|
|
VG_(sprintf)(buf, "thread: %d\n", tid);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
|
|
/* "desc:" lines */
|
|
if (!appending) {
|
|
my_fwrite(fd, "\n", 1);
|
|
|
|
#if 0
|
|
/* Global options changing the tracing behaviour */
|
|
VG_(sprintf)(buf, "\ndesc: Option: --skip-plt=%s\n",
|
|
CLG_(clo).skip_plt ? "yes" : "no");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
VG_(sprintf)(buf, "desc: Option: --collect-jumps=%s\n",
|
|
CLG_(clo).collect_jumps ? "yes" : "no");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
VG_(sprintf)(buf, "desc: Option: --separate-recs=%d\n",
|
|
CLG_(clo).separate_recursions);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
VG_(sprintf)(buf, "desc: Option: --separate-callers=%d\n",
|
|
CLG_(clo).separate_callers);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
|
|
VG_(sprintf)(buf, "desc: Option: --dump-bbs=%s\n",
|
|
CLG_(clo).dump_bbs ? "yes" : "no");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
VG_(sprintf)(buf, "desc: Option: --separate-threads=%s\n",
|
|
CLG_(clo).separate_threads ? "yes" : "no");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
#endif
|
|
|
|
(*CLG_(cachesim).getdesc)(buf);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
|
|
VG_(sprintf)(buf, "\ndesc: Timerange: Basic block %llu - %llu\n",
|
|
bbs_done, CLG_(stat).bb_executions);
|
|
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
VG_(sprintf)(buf, "desc: Trigger: %s\n",
|
|
trigger ? trigger : (Char*)"Program termination");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
|
|
#if 0
|
|
/* Output function specific config
|
|
* FIXME */
|
|
for (i = 0; i < N_FNCONFIG_ENTRIES; i++) {
|
|
fnc = fnc_table[i];
|
|
while (fnc) {
|
|
if (fnc->skip) {
|
|
VG_(sprintf)(buf, "desc: Option: --fn-skip=%s\n", fnc->name);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
if (fnc->dump_at_enter) {
|
|
VG_(sprintf)(buf, "desc: Option: --fn-dump-at-enter=%s\n",
|
|
fnc->name);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
if (fnc->dump_at_leave) {
|
|
VG_(sprintf)(buf, "desc: Option: --fn-dump-at-leave=%s\n",
|
|
fnc->name);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
if (fnc->separate_callers != CLG_(clo).separate_callers) {
|
|
VG_(sprintf)(buf, "desc: Option: --separate-callers%d=%s\n",
|
|
fnc->separate_callers, fnc->name);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
if (fnc->separate_recursions != CLG_(clo).separate_recursions) {
|
|
VG_(sprintf)(buf, "desc: Option: --separate-recs%d=%s\n",
|
|
fnc->separate_recursions, fnc->name);
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
}
|
|
fnc = fnc->next;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* "positions:" line */
|
|
VG_(sprintf)(buf, "\npositions:%s%s%s\n",
|
|
CLG_(clo).dump_instr ? " instr" : "",
|
|
CLG_(clo).dump_bb ? " bb" : "",
|
|
CLG_(clo).dump_line ? " line" : "");
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
|
|
/* "events:" line */
|
|
i = VG_(sprintf)(buf, "events: ");
|
|
CLG_(sprint_eventmapping)(buf+i, CLG_(dumpmap));
|
|
my_fwrite(fd, (void*)buf, VG_(strlen)(buf));
|
|
my_fwrite(fd, "\n", 1);
|
|
|
|
/* summary lines */
|
|
sum = CLG_(get_eventset_cost)( CLG_(sets).full );
|
|
CLG_(zero_cost)(CLG_(sets).full, sum);
|
|
if (CLG_(clo).separate_threads) {
|
|
thread_info* ti = CLG_(get_current_thread)();
|
|
CLG_(add_diff_cost)(CLG_(sets).full, sum, ti->lastdump_cost,
|
|
ti->states.entry[0]->cost);
|
|
}
|
|
else {
|
|
/* This function is called once for thread 1, where
|
|
* all costs are summed up when not dumping separate per thread.
|
|
* But this is not true for summary: we need to add all threads.
|
|
*/
|
|
int t;
|
|
thread_info** thr = CLG_(get_threads)();
|
|
for(t=1;t<VG_N_THREADS;t++) {
|
|
if (!thr[t]) continue;
|
|
CLG_(add_diff_cost)(CLG_(sets).full, sum,
|
|
thr[t]->lastdump_cost,
|
|
thr[t]->states.entry[0]->cost);
|
|
}
|
|
}
|
|
fprint_cost_ln(fd, "summary: ", CLG_(dumpmap), sum);
|
|
|
|
/* all dumped cost will be added to total_fcc */
|
|
CLG_(init_cost_lz)( CLG_(sets).full, &dump_total_cost );
|
|
|
|
my_fwrite(fd, "\n\n",2);
|
|
|
|
if (VG_(clo_verbosity) > 1)
|
|
VG_(message)(Vg_DebugMsg, "Dump to %s\n", filename);
|
|
|
|
return fd;
|
|
}
|
|
|
|
|
|
static void close_dumpfile(int fd)
|
|
{
|
|
if (fd <0) return;
|
|
|
|
fprint_cost_ln(fd, "totals: ", CLG_(dumpmap),
|
|
dump_total_cost);
|
|
//fprint_fcc_ln(fd, "summary: ", &dump_total_fcc);
|
|
CLG_(add_cost_lz)(CLG_(sets).full,
|
|
&CLG_(total_cost), dump_total_cost);
|
|
|
|
fwrite_flush();
|
|
VG_(close)(fd);
|
|
|
|
if (filename[0] == '.') {
|
|
if (-1 == VG_(rename) (filename, filename+1)) {
|
|
/* Can not rename to correct file name: give out warning */
|
|
VG_(message)(Vg_DebugMsg, "Warning: Can not rename .%s to %s\n",
|
|
filename, filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Helper for print_bbccs */
|
|
|
|
static Int print_fd;
|
|
static Char* print_trigger;
|
|
static Char print_buf[BUF_LEN];
|
|
|
|
static void print_bbccs_of_thread(thread_info* ti)
|
|
{
|
|
BBCC **p, **array;
|
|
FnPos lastFnPos;
|
|
AddrPos lastAPos;
|
|
|
|
CLG_DEBUG(1, "+ print_bbccs(tid %d)\n", CLG_(current_tid));
|
|
|
|
print_fd = new_dumpfile(print_buf, CLG_(current_tid), print_trigger);
|
|
if (print_fd <0) {
|
|
CLG_DEBUG(1, "- print_bbccs(tid %d): No output...\n", CLG_(current_tid));
|
|
return;
|
|
}
|
|
|
|
p = array = prepare_dump();
|
|
init_fpos(&lastFnPos);
|
|
init_apos(&lastAPos, 0, 0, 0);
|
|
|
|
if (p) while(1) {
|
|
|
|
/* on context/function change, print old cost buffer before */
|
|
if (lastFnPos.cxt && ((*p==0) ||
|
|
(lastFnPos.cxt != (*p)->cxt) ||
|
|
(lastFnPos.rec_index != (*p)->rec_index))) {
|
|
if (!CLG_(is_zero_cost)( CLG_(sets).full, ccSum[currSum].cost )) {
|
|
/* no need to switch buffers, as position is the same */
|
|
fprint_apos(print_fd, &(ccSum[currSum].p), &lastAPos,
|
|
lastFnPos.cxt->fn[0]->file);
|
|
fprint_fcost(print_fd, &ccSum[currSum], &lastAPos);
|
|
}
|
|
|
|
if (ccSum[currSum].p.file != lastFnPos.cxt->fn[0]->file) {
|
|
/* switch back to file of function */
|
|
VG_(sprintf)(print_buf, "fe=");
|
|
print_file(print_buf+3, lastFnPos.cxt->fn[0]->file);
|
|
my_fwrite(print_fd, (void*)print_buf, VG_(strlen)(print_buf));
|
|
}
|
|
my_fwrite(print_fd, "\n", 1);
|
|
}
|
|
|
|
if (*p == 0) break;
|
|
|
|
if (print_fn_pos(print_fd, &lastFnPos, *p)) {
|
|
|
|
/* new function */
|
|
init_apos(&lastAPos, 0, 0, (*p)->cxt->fn[0]->file);
|
|
init_fcost(&ccSum[0], 0, 0, 0);
|
|
init_fcost(&ccSum[1], 0, 0, 0);
|
|
currSum = 0;
|
|
}
|
|
|
|
if (CLG_(clo).dump_bbs) {
|
|
/* FIXME: Specify Object of BB if different to object of fn */
|
|
int i, pos = 0;
|
|
ULong ecounter = (*p)->ecounter_sum;
|
|
pos = VG_(sprintf)(print_buf, "bb=%#lx ", (*p)->bb->offset);
|
|
for(i = 0; i<(*p)->bb->cjmp_count;i++) {
|
|
pos += VG_(sprintf)(print_buf+pos, "%d %llu ",
|
|
(*p)->bb->jmp[i].instr,
|
|
ecounter);
|
|
ecounter -= (*p)->jmp[i].ecounter;
|
|
}
|
|
VG_(sprintf)(print_buf+pos, "%d %llu\n",
|
|
(*p)->bb->instr_count,
|
|
ecounter);
|
|
my_fwrite(print_fd, (void*)print_buf, VG_(strlen)(print_buf));
|
|
}
|
|
|
|
fprint_bbcc(print_fd, *p, &lastAPos);
|
|
|
|
p++;
|
|
}
|
|
|
|
close_dumpfile(print_fd);
|
|
if (array) VG_(free)(array);
|
|
|
|
/* set counters of last dump */
|
|
CLG_(copy_cost)( CLG_(sets).full, ti->lastdump_cost,
|
|
CLG_(current_state).cost );
|
|
|
|
CLG_DEBUG(1, "- print_bbccs(tid %d)\n", CLG_(current_tid));
|
|
}
|
|
|
|
|
|
static void print_bbccs(Char* trigger, Bool only_current_thread)
|
|
{
|
|
init_dump_array();
|
|
init_debug_cache();
|
|
|
|
print_fd = -1;
|
|
print_trigger = trigger;
|
|
|
|
if (!CLG_(clo).separate_threads) {
|
|
/* All BBCC/JCC costs is stored for thread 1 */
|
|
Int orig_tid = CLG_(current_tid);
|
|
|
|
CLG_(switch_thread)(1);
|
|
print_bbccs_of_thread( CLG_(get_current_thread)() );
|
|
CLG_(switch_thread)(orig_tid);
|
|
}
|
|
else if (only_current_thread)
|
|
print_bbccs_of_thread( CLG_(get_current_thread)() );
|
|
else
|
|
CLG_(forall_threads)(print_bbccs_of_thread);
|
|
|
|
free_dump_array();
|
|
}
|
|
|
|
|
|
void CLG_(dump_profile)(Char* trigger, Bool only_current_thread)
|
|
{
|
|
CLG_DEBUG(2, "+ dump_profile(Trigger '%s')\n",
|
|
trigger ? trigger : (Char*)"Prg.Term.");
|
|
|
|
CLG_(init_dumps)();
|
|
|
|
if (VG_(clo_verbosity) > 1)
|
|
VG_(message)(Vg_DebugMsg, "Start dumping at BB %llu (%s)...\n",
|
|
CLG_(stat).bb_executions,
|
|
trigger ? trigger : (Char*)"Prg.Term.");
|
|
|
|
out_counter++;
|
|
|
|
print_bbccs(trigger, only_current_thread);
|
|
|
|
bbs_done = CLG_(stat).bb_executions++;
|
|
|
|
if (VG_(clo_verbosity) > 1)
|
|
VG_(message)(Vg_DebugMsg, "Dumping done.\n");
|
|
}
|
|
|
|
/* Copy command to cmd buffer. We want to original command line
|
|
* (can change at runtime)
|
|
*/
|
|
static
|
|
void init_cmdbuf(void)
|
|
{
|
|
Int i,j,size = 0;
|
|
HChar* argv;
|
|
|
|
if (VG_(args_the_exename)) {
|
|
CLG_ASSERT( VG_(strlen)( VG_(args_the_exename) ) < BUF_LEN-1);
|
|
size = VG_(sprintf)(cmdbuf, " %s", VG_(args_the_exename));
|
|
}
|
|
|
|
for(i = 0; i < VG_(sizeXA)( VG_(args_for_client) ); i++) {
|
|
argv = * (HChar**) VG_(indexXA)( VG_(args_for_client), i );
|
|
if (!argv) continue;
|
|
if ((size>0) && (size < BUF_LEN)) cmdbuf[size++] = ' ';
|
|
for(j=0;argv[j]!=0;j++)
|
|
if (size < BUF_LEN) cmdbuf[size++] = argv[j];
|
|
}
|
|
|
|
if (size >= BUF_LEN) size = BUF_LEN-1;
|
|
cmdbuf[size] = 0;
|
|
}
|
|
|
|
/*
|
|
* Set up file names for dump output: <out_directory>, <out_file>.
|
|
* <out_file> is derived from the output format string, which defaults
|
|
* to "callgrind.out.%p", where %p is replaced with the PID.
|
|
* For the final file name, on intermediate dumps a counter is appended,
|
|
* and further, if separate dumps per thread are requested, the thread ID.
|
|
*
|
|
* <out_file> always starts with a full absolute path.
|
|
* If the output format string represents a relative path, the current
|
|
* working directory at program start is used.
|
|
*
|
|
* This function has to be called every time a profile dump is generated
|
|
* to be able to react on PID changes.
|
|
*/
|
|
void CLG_(init_dumps)()
|
|
{
|
|
Int lastSlash, i;
|
|
SysRes res;
|
|
|
|
static int thisPID = 0;
|
|
int currentPID = VG_(getpid)();
|
|
if (currentPID == thisPID) {
|
|
/* already initialized, and no PID change */
|
|
CLG_ASSERT(out_file != 0);
|
|
return;
|
|
}
|
|
thisPID = currentPID;
|
|
|
|
if (!CLG_(clo).out_format)
|
|
CLG_(clo).out_format = DEFAULT_OUTFORMAT;
|
|
|
|
/* If a file name was already set, clean up before */
|
|
if (out_file) {
|
|
VG_(free)(out_file);
|
|
VG_(free)(out_directory);
|
|
VG_(free)(filename);
|
|
out_counter = 0;
|
|
}
|
|
|
|
// Setup output filename.
|
|
out_file =
|
|
VG_(expand_file_name)("--callgrind-out-file", CLG_(clo).out_format);
|
|
|
|
/* get base directory for dump/command/result files */
|
|
CLG_ASSERT(out_file[0] == '/');
|
|
lastSlash = 0;
|
|
i = 1;
|
|
while(out_file[i]) {
|
|
if (out_file[i] == '/') lastSlash = i;
|
|
i++;
|
|
}
|
|
i = lastSlash;
|
|
out_directory = (Char*) CLG_MALLOC("cl.dump.init_dumps.1", i+1);
|
|
VG_(strncpy)(out_directory, out_file, i);
|
|
out_directory[i] = 0;
|
|
|
|
/* allocate space big enough for final filenames */
|
|
filename = (Char*) CLG_MALLOC("cl.dump.init_dumps.2",
|
|
VG_(strlen)(out_file)+32);
|
|
CLG_ASSERT(filename != 0);
|
|
|
|
/* Make sure the output base file can be written.
|
|
* This is used for the dump at program termination.
|
|
* We stop with an error here if we can not create the
|
|
* file: This is probably because of missing rights,
|
|
* and trace parts wouldn't be allowed to be written, too.
|
|
*/
|
|
VG_(strcpy)(filename, out_file);
|
|
res = VG_(open)(filename, VKI_O_WRONLY|VKI_O_TRUNC, 0);
|
|
if (sr_isError(res)) {
|
|
res = VG_(open)(filename, VKI_O_CREAT|VKI_O_WRONLY,
|
|
VKI_S_IRUSR|VKI_S_IWUSR);
|
|
if (sr_isError(res)) {
|
|
file_err();
|
|
}
|
|
}
|
|
if (!sr_isError(res)) VG_(close)( (Int)sr_Res(res) );
|
|
|
|
if (!dumps_initialized)
|
|
init_cmdbuf();
|
|
|
|
dumps_initialized = True;
|
|
}
|