mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-11 17:51:32 +00:00
split afl-fuzz: extras
This commit is contained in:
@ -468,31 +468,47 @@ void trim_py(char**, size_t*);
|
||||
|
||||
/* Queue */
|
||||
|
||||
void mark_as_det_done(struct queue_entry* q);
|
||||
void mark_as_variable(struct queue_entry* q);
|
||||
void mark_as_redundant(struct queue_entry* q, u8 state);
|
||||
void add_to_queue(u8* fname, u32 len, u8 passed_det);
|
||||
void mark_as_det_done(struct queue_entry*);
|
||||
void mark_as_variable(struct queue_entry*);
|
||||
void mark_as_redundant(struct queue_entry*, u8);
|
||||
void add_to_queue(u8*, u32, u8);
|
||||
void destroy_queue(void);
|
||||
void update_bitmap_score(struct queue_entry* q);
|
||||
void update_bitmap_score(struct queue_entry*);
|
||||
void cull_queue(void);
|
||||
|
||||
/* Bitmap */
|
||||
|
||||
void write_bitmap(void);
|
||||
void read_bitmap(u8* fname);
|
||||
u8 has_new_bits(u8* virgin_map);
|
||||
u32 count_bits(u8* mem);
|
||||
u32 count_bytes(u8* mem);
|
||||
u32 count_non_255_bytes(u8* mem);
|
||||
void read_bitmap(u8*);
|
||||
u8 has_new_bits(u8*);
|
||||
u32 count_bits(u8*);
|
||||
u32 count_bytes(u8*);
|
||||
u32 count_non_255_bytes(u8*);
|
||||
#ifdef __x86_64__
|
||||
void simplify_trace(u64* mem);
|
||||
void classify_counts(u64* mem);
|
||||
void simplify_trace(u64*);
|
||||
void classify_counts(u64*);
|
||||
#else
|
||||
void simplify_trace(u32* mem);
|
||||
void classify_counts(u32* mem);
|
||||
void simplify_trace(u32*);
|
||||
void classify_counts(u32*);
|
||||
#endif
|
||||
void init_count_class16(void);
|
||||
void minimize_bits(u8* dst, u8* src);
|
||||
void minimize_bits(u8*, u8*);
|
||||
|
||||
/* Misc */
|
||||
|
||||
u8* DI(u64);
|
||||
u8* DF(double);
|
||||
u8* DMS(u64);
|
||||
u8* DTD(u64, u64);
|
||||
|
||||
/* Extras */
|
||||
|
||||
void load_extras_file(u8*, u32*, u32*, u32);
|
||||
void load_extras(u8*);
|
||||
void maybe_add_auto(u8*, u32);
|
||||
void save_auto(void);
|
||||
void load_auto(void);
|
||||
void destroy_extras(void);
|
||||
|
||||
/**** Inline routines ****/
|
||||
|
||||
|
@ -211,159 +211,6 @@ static void locate_diffs(u8* ptr1, u8* ptr2, u32 len, s32* first, s32* last) {
|
||||
#endif /* !IGNORE_FINDS */
|
||||
|
||||
|
||||
/* Describe integer. Uses 12 cyclic static buffers for return values. The value
|
||||
returned should be five characters or less for all the integers we reasonably
|
||||
expect to see. */
|
||||
|
||||
static u8* DI(u64 val) {
|
||||
|
||||
static u8 tmp[12][16];
|
||||
static u8 cur;
|
||||
|
||||
cur = (cur + 1) % 12;
|
||||
|
||||
#define CHK_FORMAT(_divisor, _limit_mult, _fmt, _cast) do { \
|
||||
if (val < (_divisor) * (_limit_mult)) { \
|
||||
sprintf(tmp[cur], _fmt, ((_cast)val) / (_divisor)); \
|
||||
return tmp[cur]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 0-9999 */
|
||||
CHK_FORMAT(1, 10000, "%llu", u64);
|
||||
|
||||
/* 10.0k - 99.9k */
|
||||
CHK_FORMAT(1000, 99.95, "%0.01fk", double);
|
||||
|
||||
/* 100k - 999k */
|
||||
CHK_FORMAT(1000, 1000, "%lluk", u64);
|
||||
|
||||
/* 1.00M - 9.99M */
|
||||
CHK_FORMAT(1000 * 1000, 9.995, "%0.02fM", double);
|
||||
|
||||
/* 10.0M - 99.9M */
|
||||
CHK_FORMAT(1000 * 1000, 99.95, "%0.01fM", double);
|
||||
|
||||
/* 100M - 999M */
|
||||
CHK_FORMAT(1000 * 1000, 1000, "%lluM", u64);
|
||||
|
||||
/* 1.00G - 9.99G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000, 9.995, "%0.02fG", double);
|
||||
|
||||
/* 10.0G - 99.9G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000, 99.95, "%0.01fG", double);
|
||||
|
||||
/* 100G - 999G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000, 1000, "%lluG", u64);
|
||||
|
||||
/* 1.00T - 9.99G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 9.995, "%0.02fT", double);
|
||||
|
||||
/* 10.0T - 99.9T */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 99.95, "%0.01fT", double);
|
||||
|
||||
/* 100T+ */
|
||||
strcpy(tmp[cur], "infty");
|
||||
return tmp[cur];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Describe float. Similar to the above, except with a single
|
||||
static buffer. */
|
||||
|
||||
static u8* DF(double val) {
|
||||
|
||||
static u8 tmp[16];
|
||||
|
||||
if (val < 99.995) {
|
||||
sprintf(tmp, "%0.02f", val);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
if (val < 999.95) {
|
||||
sprintf(tmp, "%0.01f", val);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return DI((u64)val);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Describe integer as memory size. */
|
||||
|
||||
static u8* DMS(u64 val) {
|
||||
|
||||
static u8 tmp[12][16];
|
||||
static u8 cur;
|
||||
|
||||
cur = (cur + 1) % 12;
|
||||
|
||||
/* 0-9999 */
|
||||
CHK_FORMAT(1, 10000, "%llu B", u64);
|
||||
|
||||
/* 10.0k - 99.9k */
|
||||
CHK_FORMAT(1024, 99.95, "%0.01f kB", double);
|
||||
|
||||
/* 100k - 999k */
|
||||
CHK_FORMAT(1024, 1000, "%llu kB", u64);
|
||||
|
||||
/* 1.00M - 9.99M */
|
||||
CHK_FORMAT(1024 * 1024, 9.995, "%0.02f MB", double);
|
||||
|
||||
/* 10.0M - 99.9M */
|
||||
CHK_FORMAT(1024 * 1024, 99.95, "%0.01f MB", double);
|
||||
|
||||
/* 100M - 999M */
|
||||
CHK_FORMAT(1024 * 1024, 1000, "%llu MB", u64);
|
||||
|
||||
/* 1.00G - 9.99G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024, 9.995, "%0.02f GB", double);
|
||||
|
||||
/* 10.0G - 99.9G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024, 99.95, "%0.01f GB", double);
|
||||
|
||||
/* 100G - 999G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024, 1000, "%llu GB", u64);
|
||||
|
||||
/* 1.00T - 9.99G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 9.995, "%0.02f TB", double);
|
||||
|
||||
/* 10.0T - 99.9T */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 99.95, "%0.01f TB", double);
|
||||
|
||||
#undef CHK_FORMAT
|
||||
|
||||
/* 100T+ */
|
||||
strcpy(tmp[cur], "infty");
|
||||
return tmp[cur];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Describe time delta. Returns one static buffer, 34 chars of less. */
|
||||
|
||||
static u8* DTD(u64 cur_ms, u64 event_ms) {
|
||||
|
||||
static u8 tmp[64];
|
||||
u64 delta;
|
||||
s32 t_d, t_h, t_m, t_s;
|
||||
|
||||
if (!event_ms) return "none seen yet";
|
||||
|
||||
delta = cur_ms - event_ms;
|
||||
|
||||
t_d = delta / 1000 / 60 / 60 / 24;
|
||||
t_h = (delta / 1000 / 60 / 60) % 24;
|
||||
t_m = (delta / 1000 / 60) % 60;
|
||||
t_s = (delta / 1000) % 60;
|
||||
|
||||
sprintf(tmp, "%s days, %d hrs, %d min, %d sec", DI(t_d), t_h, t_m, t_s);
|
||||
return tmp;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Load postprocessor, if available. */
|
||||
|
||||
@ -516,468 +363,6 @@ static void read_testcases(void) {
|
||||
}
|
||||
|
||||
|
||||
/* Helper function for load_extras. */
|
||||
|
||||
static int compare_extras_len(const void* p1, const void* p2) {
|
||||
struct extra_data *e1 = (struct extra_data*)p1,
|
||||
*e2 = (struct extra_data*)p2;
|
||||
|
||||
return e1->len - e2->len;
|
||||
}
|
||||
|
||||
static int compare_extras_use_d(const void* p1, const void* p2) {
|
||||
struct extra_data *e1 = (struct extra_data*)p1,
|
||||
*e2 = (struct extra_data*)p2;
|
||||
|
||||
return e2->hit_cnt - e1->hit_cnt;
|
||||
}
|
||||
|
||||
|
||||
/* Read extras from a file, sort by size. */
|
||||
|
||||
static void load_extras_file(u8* fname, u32* min_len, u32* max_len,
|
||||
u32 dict_level) {
|
||||
|
||||
FILE* f;
|
||||
u8 buf[MAX_LINE];
|
||||
u8 *lptr;
|
||||
u32 cur_line = 0;
|
||||
|
||||
f = fopen(fname, "r");
|
||||
|
||||
if (!f) PFATAL("Unable to open '%s'", fname);
|
||||
|
||||
while ((lptr = fgets(buf, MAX_LINE, f))) {
|
||||
|
||||
u8 *rptr, *wptr;
|
||||
u32 klen = 0;
|
||||
|
||||
++cur_line;
|
||||
|
||||
/* Trim on left and right. */
|
||||
|
||||
while (isspace(*lptr)) ++lptr;
|
||||
|
||||
rptr = lptr + strlen(lptr) - 1;
|
||||
while (rptr >= lptr && isspace(*rptr)) --rptr;
|
||||
++rptr;
|
||||
*rptr = 0;
|
||||
|
||||
/* Skip empty lines and comments. */
|
||||
|
||||
if (!*lptr || *lptr == '#') continue;
|
||||
|
||||
/* All other lines must end with '"', which we can consume. */
|
||||
|
||||
--rptr;
|
||||
|
||||
if (rptr < lptr || *rptr != '"')
|
||||
FATAL("Malformed name=\"value\" pair in line %u.", cur_line);
|
||||
|
||||
*rptr = 0;
|
||||
|
||||
/* Skip alphanumerics and dashes (label). */
|
||||
|
||||
while (isalnum(*lptr) || *lptr == '_') ++lptr;
|
||||
|
||||
/* If @number follows, parse that. */
|
||||
|
||||
if (*lptr == '@') {
|
||||
|
||||
++lptr;
|
||||
if (atoi(lptr) > dict_level) continue;
|
||||
while (isdigit(*lptr)) ++lptr;
|
||||
|
||||
}
|
||||
|
||||
/* Skip whitespace and = signs. */
|
||||
|
||||
while (isspace(*lptr) || *lptr == '=') ++lptr;
|
||||
|
||||
/* Consume opening '"'. */
|
||||
|
||||
if (*lptr != '"')
|
||||
FATAL("Malformed name=\"keyword\" pair in line %u.", cur_line);
|
||||
|
||||
++lptr;
|
||||
|
||||
if (!*lptr) FATAL("Empty keyword in line %u.", cur_line);
|
||||
|
||||
/* Okay, let's allocate memory and copy data between "...", handling
|
||||
\xNN escaping, \\, and \". */
|
||||
|
||||
extras = ck_realloc_block(extras, (extras_cnt + 1) *
|
||||
sizeof(struct extra_data));
|
||||
|
||||
wptr = extras[extras_cnt].data = ck_alloc(rptr - lptr);
|
||||
|
||||
while (*lptr) {
|
||||
|
||||
char* hexdigits = "0123456789abcdef";
|
||||
|
||||
switch (*lptr) {
|
||||
|
||||
case 1 ... 31:
|
||||
case 128 ... 255:
|
||||
FATAL("Non-printable characters in line %u.", cur_line);
|
||||
|
||||
case '\\':
|
||||
|
||||
++lptr;
|
||||
|
||||
if (*lptr == '\\' || *lptr == '"') {
|
||||
*(wptr++) = *(lptr++);
|
||||
klen++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*lptr != 'x' || !isxdigit(lptr[1]) || !isxdigit(lptr[2]))
|
||||
FATAL("Invalid escaping (not \\xNN) in line %u.", cur_line);
|
||||
|
||||
*(wptr++) =
|
||||
((strchr(hexdigits, tolower(lptr[1])) - hexdigits) << 4) |
|
||||
(strchr(hexdigits, tolower(lptr[2])) - hexdigits);
|
||||
|
||||
lptr += 3;
|
||||
++klen;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
*(wptr++) = *(lptr++);
|
||||
++klen;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extras[extras_cnt].len = klen;
|
||||
|
||||
if (extras[extras_cnt].len > MAX_DICT_FILE)
|
||||
FATAL("Keyword too big in line %u (%s, limit is %s)", cur_line,
|
||||
DMS(klen), DMS(MAX_DICT_FILE));
|
||||
|
||||
if (*min_len > klen) *min_len = klen;
|
||||
if (*max_len < klen) *max_len = klen;
|
||||
|
||||
++extras_cnt;
|
||||
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Read extras from the extras directory and sort them by size. */
|
||||
|
||||
static void load_extras(u8* dir) {
|
||||
|
||||
DIR* d;
|
||||
struct dirent* de;
|
||||
u32 min_len = MAX_DICT_FILE, max_len = 0, dict_level = 0;
|
||||
u8* x;
|
||||
|
||||
/* If the name ends with @, extract level and continue. */
|
||||
|
||||
if ((x = strchr(dir, '@'))) {
|
||||
|
||||
*x = 0;
|
||||
dict_level = atoi(x + 1);
|
||||
|
||||
}
|
||||
|
||||
ACTF("Loading extra dictionary from '%s' (level %u)...", dir, dict_level);
|
||||
|
||||
d = opendir(dir);
|
||||
|
||||
if (!d) {
|
||||
|
||||
if (errno == ENOTDIR) {
|
||||
load_extras_file(dir, &min_len, &max_len, dict_level);
|
||||
goto check_and_sort;
|
||||
}
|
||||
|
||||
PFATAL("Unable to open '%s'", dir);
|
||||
|
||||
}
|
||||
|
||||
if (x) FATAL("Dictionary levels not supported for directories.");
|
||||
|
||||
while ((de = readdir(d))) {
|
||||
|
||||
struct stat st;
|
||||
u8* fn = alloc_printf("%s/%s", dir, de->d_name);
|
||||
s32 fd;
|
||||
|
||||
if (lstat(fn, &st) || access(fn, R_OK))
|
||||
PFATAL("Unable to access '%s'", fn);
|
||||
|
||||
/* This also takes care of . and .. */
|
||||
if (!S_ISREG(st.st_mode) || !st.st_size) {
|
||||
|
||||
ck_free(fn);
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (st.st_size > MAX_DICT_FILE)
|
||||
FATAL("Extra '%s' is too big (%s, limit is %s)", fn,
|
||||
DMS(st.st_size), DMS(MAX_DICT_FILE));
|
||||
|
||||
if (min_len > st.st_size) min_len = st.st_size;
|
||||
if (max_len < st.st_size) max_len = st.st_size;
|
||||
|
||||
extras = ck_realloc_block(extras, (extras_cnt + 1) *
|
||||
sizeof(struct extra_data));
|
||||
|
||||
extras[extras_cnt].data = ck_alloc(st.st_size);
|
||||
extras[extras_cnt].len = st.st_size;
|
||||
|
||||
fd = open(fn, O_RDONLY);
|
||||
|
||||
if (fd < 0) PFATAL("Unable to open '%s'", fn);
|
||||
|
||||
ck_read(fd, extras[extras_cnt].data, st.st_size, fn);
|
||||
|
||||
close(fd);
|
||||
ck_free(fn);
|
||||
|
||||
++extras_cnt;
|
||||
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
|
||||
check_and_sort:
|
||||
|
||||
if (!extras_cnt) FATAL("No usable files in '%s'", dir);
|
||||
|
||||
qsort(extras, extras_cnt, sizeof(struct extra_data), compare_extras_len);
|
||||
|
||||
OKF("Loaded %u extra tokens, size range %s to %s.", extras_cnt,
|
||||
DMS(min_len), DMS(max_len));
|
||||
|
||||
if (max_len > 32)
|
||||
WARNF("Some tokens are relatively large (%s) - consider trimming.",
|
||||
DMS(max_len));
|
||||
|
||||
if (extras_cnt > MAX_DET_EXTRAS)
|
||||
WARNF("More than %d tokens - will use them probabilistically.",
|
||||
MAX_DET_EXTRAS);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Helper function for maybe_add_auto() */
|
||||
|
||||
static inline u8 memcmp_nocase(u8* m1, u8* m2, u32 len) {
|
||||
|
||||
while (len--) if (tolower(*(m1++)) ^ tolower(*(m2++))) return 1;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Maybe add automatic extra. */
|
||||
|
||||
static void maybe_add_auto(u8* mem, u32 len) {
|
||||
|
||||
u32 i;
|
||||
|
||||
/* Allow users to specify that they don't want auto dictionaries. */
|
||||
|
||||
if (!MAX_AUTO_EXTRAS || !USE_AUTO_EXTRAS) return;
|
||||
|
||||
/* Skip runs of identical bytes. */
|
||||
|
||||
for (i = 1; i < len; ++i)
|
||||
if (mem[0] ^ mem[i]) break;
|
||||
|
||||
if (i == len) return;
|
||||
|
||||
/* Reject builtin interesting values. */
|
||||
|
||||
if (len == 2) {
|
||||
|
||||
i = sizeof(interesting_16) >> 1;
|
||||
|
||||
while (i--)
|
||||
if (*((u16*)mem) == interesting_16[i] ||
|
||||
*((u16*)mem) == SWAP16(interesting_16[i])) return;
|
||||
|
||||
}
|
||||
|
||||
if (len == 4) {
|
||||
|
||||
i = sizeof(interesting_32) >> 2;
|
||||
|
||||
while (i--)
|
||||
if (*((u32*)mem) == interesting_32[i] ||
|
||||
*((u32*)mem) == SWAP32(interesting_32[i])) return;
|
||||
|
||||
}
|
||||
|
||||
/* Reject anything that matches existing extras. Do a case-insensitive
|
||||
match. We optimize by exploiting the fact that extras[] are sorted
|
||||
by size. */
|
||||
|
||||
for (i = 0; i < extras_cnt; ++i)
|
||||
if (extras[i].len >= len) break;
|
||||
|
||||
for (; i < extras_cnt && extras[i].len == len; ++i)
|
||||
if (!memcmp_nocase(extras[i].data, mem, len)) return;
|
||||
|
||||
/* Last but not least, check a_extras[] for matches. There are no
|
||||
guarantees of a particular sort order. */
|
||||
|
||||
auto_changed = 1;
|
||||
|
||||
for (i = 0; i < a_extras_cnt; ++i) {
|
||||
|
||||
if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) {
|
||||
|
||||
a_extras[i].hit_cnt++;
|
||||
goto sort_a_extras;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* At this point, looks like we're dealing with a new entry. So, let's
|
||||
append it if we have room. Otherwise, let's randomly evict some other
|
||||
entry from the bottom half of the list. */
|
||||
|
||||
if (a_extras_cnt < MAX_AUTO_EXTRAS) {
|
||||
|
||||
a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) *
|
||||
sizeof(struct extra_data));
|
||||
|
||||
a_extras[a_extras_cnt].data = ck_memdup(mem, len);
|
||||
a_extras[a_extras_cnt].len = len;
|
||||
++a_extras_cnt;
|
||||
|
||||
} else {
|
||||
|
||||
i = MAX_AUTO_EXTRAS / 2 +
|
||||
UR((MAX_AUTO_EXTRAS + 1) / 2);
|
||||
|
||||
ck_free(a_extras[i].data);
|
||||
|
||||
a_extras[i].data = ck_memdup(mem, len);
|
||||
a_extras[i].len = len;
|
||||
a_extras[i].hit_cnt = 0;
|
||||
|
||||
}
|
||||
|
||||
sort_a_extras:
|
||||
|
||||
/* First, sort all auto extras by use count, descending order. */
|
||||
|
||||
qsort(a_extras, a_extras_cnt, sizeof(struct extra_data),
|
||||
compare_extras_use_d);
|
||||
|
||||
/* Then, sort the top USE_AUTO_EXTRAS entries by size. */
|
||||
|
||||
qsort(a_extras, MIN(USE_AUTO_EXTRAS, a_extras_cnt),
|
||||
sizeof(struct extra_data), compare_extras_len);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Save automatically generated extras. */
|
||||
|
||||
static void save_auto(void) {
|
||||
|
||||
u32 i;
|
||||
|
||||
if (!auto_changed) return;
|
||||
auto_changed = 0;
|
||||
|
||||
for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); ++i) {
|
||||
|
||||
u8* fn = alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, i);
|
||||
s32 fd;
|
||||
|
||||
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
|
||||
if (fd < 0) PFATAL("Unable to create '%s'", fn);
|
||||
|
||||
ck_write(fd, a_extras[i].data, a_extras[i].len, fn);
|
||||
|
||||
close(fd);
|
||||
ck_free(fn);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Load automatically generated extras. */
|
||||
|
||||
static void load_auto(void) {
|
||||
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < USE_AUTO_EXTRAS; ++i) {
|
||||
|
||||
u8 tmp[MAX_AUTO_EXTRA + 1];
|
||||
u8* fn = alloc_printf("%s/.state/auto_extras/auto_%06u", in_dir, i);
|
||||
s32 fd, len;
|
||||
|
||||
fd = open(fn, O_RDONLY, 0600);
|
||||
|
||||
if (fd < 0) {
|
||||
|
||||
if (errno != ENOENT) PFATAL("Unable to open '%s'", fn);
|
||||
ck_free(fn);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/* We read one byte more to cheaply detect tokens that are too
|
||||
long (and skip them). */
|
||||
|
||||
len = read(fd, tmp, MAX_AUTO_EXTRA + 1);
|
||||
|
||||
if (len < 0) PFATAL("Unable to read from '%s'", fn);
|
||||
|
||||
if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA)
|
||||
maybe_add_auto(tmp, len);
|
||||
|
||||
close(fd);
|
||||
ck_free(fn);
|
||||
|
||||
}
|
||||
|
||||
if (i) OKF("Loaded %u auto-discovered dictionary tokens.", i);
|
||||
else OKF("No auto-generated dictionary tokens to reuse.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Destroy extras. */
|
||||
|
||||
static void destroy_extras(void) {
|
||||
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < extras_cnt; ++i)
|
||||
ck_free(extras[i].data);
|
||||
|
||||
ck_free(extras);
|
||||
|
||||
for (i = 0; i < a_extras_cnt; ++i)
|
||||
ck_free(a_extras[i].data);
|
||||
|
||||
ck_free(a_extras);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Execute target application, monitoring for timeouts. Return status
|
||||
information. The called program will update trace_bits[]. */
|
||||
|
||||
|
484
src/afl-fuzz-src/extras.c
Normal file
484
src/afl-fuzz-src/extras.c
Normal file
@ -0,0 +1,484 @@
|
||||
/*
|
||||
american fuzzy lop - fuzzer code
|
||||
--------------------------------
|
||||
|
||||
Written and maintained by Michal Zalewski <lcamtuf@google.com>
|
||||
|
||||
Forkserver design by Jann Horn <jannhorn@googlemail.com>
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016, 2017 Google Inc. All rights reserved.
|
||||
|
||||
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
|
||||
|
||||
This is the real deal: the program takes an instrumented binary and
|
||||
attempts a variety of basic fuzzing tricks, paying close attention to
|
||||
how they affect the execution path.
|
||||
|
||||
*/
|
||||
|
||||
#include "afl-fuzz.h"
|
||||
|
||||
|
||||
/* Helper function for load_extras. */
|
||||
|
||||
static int compare_extras_len(const void* p1, const void* p2) {
|
||||
struct extra_data *e1 = (struct extra_data*)p1,
|
||||
*e2 = (struct extra_data*)p2;
|
||||
|
||||
return e1->len - e2->len;
|
||||
}
|
||||
|
||||
static int compare_extras_use_d(const void* p1, const void* p2) {
|
||||
struct extra_data *e1 = (struct extra_data*)p1,
|
||||
*e2 = (struct extra_data*)p2;
|
||||
|
||||
return e2->hit_cnt - e1->hit_cnt;
|
||||
}
|
||||
|
||||
|
||||
/* Read extras from a file, sort by size. */
|
||||
|
||||
void load_extras_file(u8* fname, u32* min_len, u32* max_len, u32 dict_level) {
|
||||
|
||||
FILE* f;
|
||||
u8 buf[MAX_LINE];
|
||||
u8 *lptr;
|
||||
u32 cur_line = 0;
|
||||
|
||||
f = fopen(fname, "r");
|
||||
|
||||
if (!f) PFATAL("Unable to open '%s'", fname);
|
||||
|
||||
while ((lptr = fgets(buf, MAX_LINE, f))) {
|
||||
|
||||
u8 *rptr, *wptr;
|
||||
u32 klen = 0;
|
||||
|
||||
++cur_line;
|
||||
|
||||
/* Trim on left and right. */
|
||||
|
||||
while (isspace(*lptr)) ++lptr;
|
||||
|
||||
rptr = lptr + strlen(lptr) - 1;
|
||||
while (rptr >= lptr && isspace(*rptr)) --rptr;
|
||||
++rptr;
|
||||
*rptr = 0;
|
||||
|
||||
/* Skip empty lines and comments. */
|
||||
|
||||
if (!*lptr || *lptr == '#') continue;
|
||||
|
||||
/* All other lines must end with '"', which we can consume. */
|
||||
|
||||
--rptr;
|
||||
|
||||
if (rptr < lptr || *rptr != '"')
|
||||
FATAL("Malformed name=\"value\" pair in line %u.", cur_line);
|
||||
|
||||
*rptr = 0;
|
||||
|
||||
/* Skip alphanumerics and dashes (label). */
|
||||
|
||||
while (isalnum(*lptr) || *lptr == '_') ++lptr;
|
||||
|
||||
/* If @number follows, parse that. */
|
||||
|
||||
if (*lptr == '@') {
|
||||
|
||||
++lptr;
|
||||
if (atoi(lptr) > dict_level) continue;
|
||||
while (isdigit(*lptr)) ++lptr;
|
||||
|
||||
}
|
||||
|
||||
/* Skip whitespace and = signs. */
|
||||
|
||||
while (isspace(*lptr) || *lptr == '=') ++lptr;
|
||||
|
||||
/* Consume opening '"'. */
|
||||
|
||||
if (*lptr != '"')
|
||||
FATAL("Malformed name=\"keyword\" pair in line %u.", cur_line);
|
||||
|
||||
++lptr;
|
||||
|
||||
if (!*lptr) FATAL("Empty keyword in line %u.", cur_line);
|
||||
|
||||
/* Okay, let's allocate memory and copy data between "...", handling
|
||||
\xNN escaping, \\, and \". */
|
||||
|
||||
extras = ck_realloc_block(extras, (extras_cnt + 1) *
|
||||
sizeof(struct extra_data));
|
||||
|
||||
wptr = extras[extras_cnt].data = ck_alloc(rptr - lptr);
|
||||
|
||||
while (*lptr) {
|
||||
|
||||
char* hexdigits = "0123456789abcdef";
|
||||
|
||||
switch (*lptr) {
|
||||
|
||||
case 1 ... 31:
|
||||
case 128 ... 255:
|
||||
FATAL("Non-printable characters in line %u.", cur_line);
|
||||
|
||||
case '\\':
|
||||
|
||||
++lptr;
|
||||
|
||||
if (*lptr == '\\' || *lptr == '"') {
|
||||
*(wptr++) = *(lptr++);
|
||||
klen++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*lptr != 'x' || !isxdigit(lptr[1]) || !isxdigit(lptr[2]))
|
||||
FATAL("Invalid escaping (not \\xNN) in line %u.", cur_line);
|
||||
|
||||
*(wptr++) =
|
||||
((strchr(hexdigits, tolower(lptr[1])) - hexdigits) << 4) |
|
||||
(strchr(hexdigits, tolower(lptr[2])) - hexdigits);
|
||||
|
||||
lptr += 3;
|
||||
++klen;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
*(wptr++) = *(lptr++);
|
||||
++klen;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extras[extras_cnt].len = klen;
|
||||
|
||||
if (extras[extras_cnt].len > MAX_DICT_FILE)
|
||||
FATAL("Keyword too big in line %u (%s, limit is %s)", cur_line,
|
||||
DMS(klen), DMS(MAX_DICT_FILE));
|
||||
|
||||
if (*min_len > klen) *min_len = klen;
|
||||
if (*max_len < klen) *max_len = klen;
|
||||
|
||||
++extras_cnt;
|
||||
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Read extras from the extras directory and sort them by size. */
|
||||
|
||||
void load_extras(u8* dir) {
|
||||
|
||||
DIR* d;
|
||||
struct dirent* de;
|
||||
u32 min_len = MAX_DICT_FILE, max_len = 0, dict_level = 0;
|
||||
u8* x;
|
||||
|
||||
/* If the name ends with @, extract level and continue. */
|
||||
|
||||
if ((x = strchr(dir, '@'))) {
|
||||
|
||||
*x = 0;
|
||||
dict_level = atoi(x + 1);
|
||||
|
||||
}
|
||||
|
||||
ACTF("Loading extra dictionary from '%s' (level %u)...", dir, dict_level);
|
||||
|
||||
d = opendir(dir);
|
||||
|
||||
if (!d) {
|
||||
|
||||
if (errno == ENOTDIR) {
|
||||
load_extras_file(dir, &min_len, &max_len, dict_level);
|
||||
goto check_and_sort;
|
||||
}
|
||||
|
||||
PFATAL("Unable to open '%s'", dir);
|
||||
|
||||
}
|
||||
|
||||
if (x) FATAL("Dictionary levels not supported for directories.");
|
||||
|
||||
while ((de = readdir(d))) {
|
||||
|
||||
struct stat st;
|
||||
u8* fn = alloc_printf("%s/%s", dir, de->d_name);
|
||||
s32 fd;
|
||||
|
||||
if (lstat(fn, &st) || access(fn, R_OK))
|
||||
PFATAL("Unable to access '%s'", fn);
|
||||
|
||||
/* This also takes care of . and .. */
|
||||
if (!S_ISREG(st.st_mode) || !st.st_size) {
|
||||
|
||||
ck_free(fn);
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (st.st_size > MAX_DICT_FILE)
|
||||
FATAL("Extra '%s' is too big (%s, limit is %s)", fn,
|
||||
DMS(st.st_size), DMS(MAX_DICT_FILE));
|
||||
|
||||
if (min_len > st.st_size) min_len = st.st_size;
|
||||
if (max_len < st.st_size) max_len = st.st_size;
|
||||
|
||||
extras = ck_realloc_block(extras, (extras_cnt + 1) *
|
||||
sizeof(struct extra_data));
|
||||
|
||||
extras[extras_cnt].data = ck_alloc(st.st_size);
|
||||
extras[extras_cnt].len = st.st_size;
|
||||
|
||||
fd = open(fn, O_RDONLY);
|
||||
|
||||
if (fd < 0) PFATAL("Unable to open '%s'", fn);
|
||||
|
||||
ck_read(fd, extras[extras_cnt].data, st.st_size, fn);
|
||||
|
||||
close(fd);
|
||||
ck_free(fn);
|
||||
|
||||
++extras_cnt;
|
||||
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
|
||||
check_and_sort:
|
||||
|
||||
if (!extras_cnt) FATAL("No usable files in '%s'", dir);
|
||||
|
||||
qsort(extras, extras_cnt, sizeof(struct extra_data), compare_extras_len);
|
||||
|
||||
OKF("Loaded %u extra tokens, size range %s to %s.", extras_cnt,
|
||||
DMS(min_len), DMS(max_len));
|
||||
|
||||
if (max_len > 32)
|
||||
WARNF("Some tokens are relatively large (%s) - consider trimming.",
|
||||
DMS(max_len));
|
||||
|
||||
if (extras_cnt > MAX_DET_EXTRAS)
|
||||
WARNF("More than %d tokens - will use them probabilistically.",
|
||||
MAX_DET_EXTRAS);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Helper function for maybe_add_auto() */
|
||||
|
||||
static inline u8 memcmp_nocase(u8* m1, u8* m2, u32 len) {
|
||||
|
||||
while (len--) if (tolower(*(m1++)) ^ tolower(*(m2++))) return 1;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Maybe add automatic extra. */
|
||||
|
||||
void maybe_add_auto(u8* mem, u32 len) {
|
||||
|
||||
u32 i;
|
||||
|
||||
/* Allow users to specify that they don't want auto dictionaries. */
|
||||
|
||||
if (!MAX_AUTO_EXTRAS || !USE_AUTO_EXTRAS) return;
|
||||
|
||||
/* Skip runs of identical bytes. */
|
||||
|
||||
for (i = 1; i < len; ++i)
|
||||
if (mem[0] ^ mem[i]) break;
|
||||
|
||||
if (i == len) return;
|
||||
|
||||
/* Reject builtin interesting values. */
|
||||
|
||||
if (len == 2) {
|
||||
|
||||
i = sizeof(interesting_16) >> 1;
|
||||
|
||||
while (i--)
|
||||
if (*((u16*)mem) == interesting_16[i] ||
|
||||
*((u16*)mem) == SWAP16(interesting_16[i])) return;
|
||||
|
||||
}
|
||||
|
||||
if (len == 4) {
|
||||
|
||||
i = sizeof(interesting_32) >> 2;
|
||||
|
||||
while (i--)
|
||||
if (*((u32*)mem) == interesting_32[i] ||
|
||||
*((u32*)mem) == SWAP32(interesting_32[i])) return;
|
||||
|
||||
}
|
||||
|
||||
/* Reject anything that matches existing extras. Do a case-insensitive
|
||||
match. We optimize by exploiting the fact that extras[] are sorted
|
||||
by size. */
|
||||
|
||||
for (i = 0; i < extras_cnt; ++i)
|
||||
if (extras[i].len >= len) break;
|
||||
|
||||
for (; i < extras_cnt && extras[i].len == len; ++i)
|
||||
if (!memcmp_nocase(extras[i].data, mem, len)) return;
|
||||
|
||||
/* Last but not least, check a_extras[] for matches. There are no
|
||||
guarantees of a particular sort order. */
|
||||
|
||||
auto_changed = 1;
|
||||
|
||||
for (i = 0; i < a_extras_cnt; ++i) {
|
||||
|
||||
if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) {
|
||||
|
||||
a_extras[i].hit_cnt++;
|
||||
goto sort_a_extras;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* At this point, looks like we're dealing with a new entry. So, let's
|
||||
append it if we have room. Otherwise, let's randomly evict some other
|
||||
entry from the bottom half of the list. */
|
||||
|
||||
if (a_extras_cnt < MAX_AUTO_EXTRAS) {
|
||||
|
||||
a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) *
|
||||
sizeof(struct extra_data));
|
||||
|
||||
a_extras[a_extras_cnt].data = ck_memdup(mem, len);
|
||||
a_extras[a_extras_cnt].len = len;
|
||||
++a_extras_cnt;
|
||||
|
||||
} else {
|
||||
|
||||
i = MAX_AUTO_EXTRAS / 2 +
|
||||
UR((MAX_AUTO_EXTRAS + 1) / 2);
|
||||
|
||||
ck_free(a_extras[i].data);
|
||||
|
||||
a_extras[i].data = ck_memdup(mem, len);
|
||||
a_extras[i].len = len;
|
||||
a_extras[i].hit_cnt = 0;
|
||||
|
||||
}
|
||||
|
||||
sort_a_extras:
|
||||
|
||||
/* First, sort all auto extras by use count, descending order. */
|
||||
|
||||
qsort(a_extras, a_extras_cnt, sizeof(struct extra_data),
|
||||
compare_extras_use_d);
|
||||
|
||||
/* Then, sort the top USE_AUTO_EXTRAS entries by size. */
|
||||
|
||||
qsort(a_extras, MIN(USE_AUTO_EXTRAS, a_extras_cnt),
|
||||
sizeof(struct extra_data), compare_extras_len);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Save automatically generated extras. */
|
||||
|
||||
void save_auto(void) {
|
||||
|
||||
u32 i;
|
||||
|
||||
if (!auto_changed) return;
|
||||
auto_changed = 0;
|
||||
|
||||
for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); ++i) {
|
||||
|
||||
u8* fn = alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, i);
|
||||
s32 fd;
|
||||
|
||||
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
|
||||
if (fd < 0) PFATAL("Unable to create '%s'", fn);
|
||||
|
||||
ck_write(fd, a_extras[i].data, a_extras[i].len, fn);
|
||||
|
||||
close(fd);
|
||||
ck_free(fn);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Load automatically generated extras. */
|
||||
|
||||
void load_auto(void) {
|
||||
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < USE_AUTO_EXTRAS; ++i) {
|
||||
|
||||
u8 tmp[MAX_AUTO_EXTRA + 1];
|
||||
u8* fn = alloc_printf("%s/.state/auto_extras/auto_%06u", in_dir, i);
|
||||
s32 fd, len;
|
||||
|
||||
fd = open(fn, O_RDONLY, 0600);
|
||||
|
||||
if (fd < 0) {
|
||||
|
||||
if (errno != ENOENT) PFATAL("Unable to open '%s'", fn);
|
||||
ck_free(fn);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/* We read one byte more to cheaply detect tokens that are too
|
||||
long (and skip them). */
|
||||
|
||||
len = read(fd, tmp, MAX_AUTO_EXTRA + 1);
|
||||
|
||||
if (len < 0) PFATAL("Unable to read from '%s'", fn);
|
||||
|
||||
if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA)
|
||||
maybe_add_auto(tmp, len);
|
||||
|
||||
close(fd);
|
||||
ck_free(fn);
|
||||
|
||||
}
|
||||
|
||||
if (i) OKF("Loaded %u auto-discovered dictionary tokens.", i);
|
||||
else OKF("No auto-generated dictionary tokens to reuse.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Destroy extras. */
|
||||
|
||||
void destroy_extras(void) {
|
||||
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < extras_cnt; ++i)
|
||||
ck_free(extras[i].data);
|
||||
|
||||
ck_free(extras);
|
||||
|
||||
for (i = 0; i < a_extras_cnt; ++i)
|
||||
ck_free(a_extras[i].data);
|
||||
|
||||
ck_free(a_extras);
|
||||
|
||||
}
|
||||
|
@ -22,3 +22,155 @@
|
||||
|
||||
#include "afl-fuzz.h"
|
||||
|
||||
/* Describe integer. Uses 12 cyclic static buffers for return values. The value
|
||||
returned should be five characters or less for all the integers we reasonably
|
||||
expect to see. */
|
||||
|
||||
u8* DI(u64 val) {
|
||||
|
||||
static u8 tmp[12][16];
|
||||
static u8 cur;
|
||||
|
||||
cur = (cur + 1) % 12;
|
||||
|
||||
#define CHK_FORMAT(_divisor, _limit_mult, _fmt, _cast) do { \
|
||||
if (val < (_divisor) * (_limit_mult)) { \
|
||||
sprintf(tmp[cur], _fmt, ((_cast)val) / (_divisor)); \
|
||||
return tmp[cur]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 0-9999 */
|
||||
CHK_FORMAT(1, 10000, "%llu", u64);
|
||||
|
||||
/* 10.0k - 99.9k */
|
||||
CHK_FORMAT(1000, 99.95, "%0.01fk", double);
|
||||
|
||||
/* 100k - 999k */
|
||||
CHK_FORMAT(1000, 1000, "%lluk", u64);
|
||||
|
||||
/* 1.00M - 9.99M */
|
||||
CHK_FORMAT(1000 * 1000, 9.995, "%0.02fM", double);
|
||||
|
||||
/* 10.0M - 99.9M */
|
||||
CHK_FORMAT(1000 * 1000, 99.95, "%0.01fM", double);
|
||||
|
||||
/* 100M - 999M */
|
||||
CHK_FORMAT(1000 * 1000, 1000, "%lluM", u64);
|
||||
|
||||
/* 1.00G - 9.99G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000, 9.995, "%0.02fG", double);
|
||||
|
||||
/* 10.0G - 99.9G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000, 99.95, "%0.01fG", double);
|
||||
|
||||
/* 100G - 999G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000, 1000, "%lluG", u64);
|
||||
|
||||
/* 1.00T - 9.99G */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 9.995, "%0.02fT", double);
|
||||
|
||||
/* 10.0T - 99.9T */
|
||||
CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 99.95, "%0.01fT", double);
|
||||
|
||||
/* 100T+ */
|
||||
strcpy(tmp[cur], "infty");
|
||||
return tmp[cur];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Describe float. Similar to the above, except with a single
|
||||
static buffer. */
|
||||
|
||||
u8* DF(double val) {
|
||||
|
||||
static u8 tmp[16];
|
||||
|
||||
if (val < 99.995) {
|
||||
sprintf(tmp, "%0.02f", val);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
if (val < 999.95) {
|
||||
sprintf(tmp, "%0.01f", val);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return DI((u64)val);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Describe integer as memory size. */
|
||||
|
||||
u8* DMS(u64 val) {
|
||||
|
||||
static u8 tmp[12][16];
|
||||
static u8 cur;
|
||||
|
||||
cur = (cur + 1) % 12;
|
||||
|
||||
/* 0-9999 */
|
||||
CHK_FORMAT(1, 10000, "%llu B", u64);
|
||||
|
||||
/* 10.0k - 99.9k */
|
||||
CHK_FORMAT(1024, 99.95, "%0.01f kB", double);
|
||||
|
||||
/* 100k - 999k */
|
||||
CHK_FORMAT(1024, 1000, "%llu kB", u64);
|
||||
|
||||
/* 1.00M - 9.99M */
|
||||
CHK_FORMAT(1024 * 1024, 9.995, "%0.02f MB", double);
|
||||
|
||||
/* 10.0M - 99.9M */
|
||||
CHK_FORMAT(1024 * 1024, 99.95, "%0.01f MB", double);
|
||||
|
||||
/* 100M - 999M */
|
||||
CHK_FORMAT(1024 * 1024, 1000, "%llu MB", u64);
|
||||
|
||||
/* 1.00G - 9.99G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024, 9.995, "%0.02f GB", double);
|
||||
|
||||
/* 10.0G - 99.9G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024, 99.95, "%0.01f GB", double);
|
||||
|
||||
/* 100G - 999G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024, 1000, "%llu GB", u64);
|
||||
|
||||
/* 1.00T - 9.99G */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 9.995, "%0.02f TB", double);
|
||||
|
||||
/* 10.0T - 99.9T */
|
||||
CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 99.95, "%0.01f TB", double);
|
||||
|
||||
#undef CHK_FORMAT
|
||||
|
||||
/* 100T+ */
|
||||
strcpy(tmp[cur], "infty");
|
||||
return tmp[cur];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Describe time delta. Returns one static buffer, 34 chars of less. */
|
||||
|
||||
u8* DTD(u64 cur_ms, u64 event_ms) {
|
||||
|
||||
static u8 tmp[64];
|
||||
u64 delta;
|
||||
s32 t_d, t_h, t_m, t_s;
|
||||
|
||||
if (!event_ms) return "none seen yet";
|
||||
|
||||
delta = cur_ms - event_ms;
|
||||
|
||||
t_d = delta / 1000 / 60 / 60 / 24;
|
||||
t_h = (delta / 1000 / 60 / 60) % 24;
|
||||
t_m = (delta / 1000 / 60) % 60;
|
||||
t_s = (delta / 1000) % 60;
|
||||
|
||||
sprintf(tmp, "%s days, %d hrs, %d min, %d sec", DI(t_d), t_h, t_m, t_s);
|
||||
return tmp;
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user