Tests: detect task leaks (#5528)
This commit is contained in:
parent
d11fd21cd6
commit
1421ed1b24
6 changed files with 3350 additions and 13 deletions
|
@ -45,5 +45,6 @@ void SE12PanpotControl(s8 pan);
|
|||
bool8 IsSEPlaying(void);
|
||||
bool8 IsBGMPlaying(void);
|
||||
bool8 IsSpecialSEPlaying(void);
|
||||
void Task_DuckBGMForPokemonCry(u8 taskId);
|
||||
|
||||
#endif // GUARD_SOUND_H
|
||||
|
|
|
@ -1780,7 +1780,20 @@ void CB2_QuitRecordedBattle(void)
|
|||
m4aMPlayStop(&gMPlayInfo_SE1);
|
||||
m4aMPlayStop(&gMPlayInfo_SE2);
|
||||
if (gTestRunnerEnabled)
|
||||
{
|
||||
// Clean up potentially-leaking tasks.
|
||||
// I think these leak when the battle ends soon after a
|
||||
// battler is fainted.
|
||||
u8 taskId;
|
||||
taskId = FindTaskIdByFunc(Task_PlayerController_RestoreBgmAfterCry);
|
||||
if (taskId != TASK_NONE)
|
||||
DestroyTask(taskId);
|
||||
taskId = FindTaskIdByFunc(Task_DuckBGMForPokemonCry);
|
||||
if (taskId != TASK_NONE)
|
||||
DestroyTask(taskId);
|
||||
|
||||
TestRunner_Battle_AfterLastTurn();
|
||||
}
|
||||
FreeRestoreBattleData();
|
||||
FreeAllWindowBuffers();
|
||||
SetMainCallback2(gMain.savedCallback);
|
||||
|
|
|
@ -31,7 +31,6 @@ extern struct ToneData gCryTable_Reverse[];
|
|||
|
||||
static void Task_Fanfare(u8 taskId);
|
||||
static void CreateFanfareTask(void);
|
||||
static void Task_DuckBGMForPokemonCry(u8 taskId);
|
||||
static void RestoreBGMVolumeAfterPokemonCry(void);
|
||||
|
||||
static const struct Fanfare sFanfares[] = {
|
||||
|
@ -513,7 +512,7 @@ bool8 IsCryPlaying(void)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
static void Task_DuckBGMForPokemonCry(u8 taskId)
|
||||
void Task_DuckBGMForPokemonCry(u8 taskId)
|
||||
{
|
||||
if (gPokemonCryBGMDuckingCounter)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "main.h"
|
||||
#include "malloc.h"
|
||||
#include "random.h"
|
||||
#include "task.h"
|
||||
#include "constants/characters.h"
|
||||
#include "test_runner.h"
|
||||
#include "test/test.h"
|
||||
|
@ -190,6 +191,7 @@ top:
|
|||
else
|
||||
gTestRunnerState.timeoutSeconds = UINT_MAX;
|
||||
InitHeap(gHeap, HEAP_SIZE);
|
||||
ResetTasks();
|
||||
EnableInterrupts(INTR_FLAG_TIMER2);
|
||||
REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second.
|
||||
REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK;
|
||||
|
@ -243,6 +245,7 @@ top:
|
|||
if (gTestRunnerState.result == TEST_RESULT_PASS
|
||||
&& !gTestRunnerState.expectLeaks)
|
||||
{
|
||||
int i;
|
||||
const struct MemBlock *head = HeapHead();
|
||||
const struct MemBlock *block = head;
|
||||
do
|
||||
|
@ -251,7 +254,7 @@ top:
|
|||
|| !(EWRAM_START <= (uintptr_t)block->next && (uintptr_t)block->next < EWRAM_END)
|
||||
|| (block->next <= block && block->next != head))
|
||||
{
|
||||
Test_MgbaPrintf("gHeap corrupted block at 0x%p", block);
|
||||
Test_MgbaPrintf("gHeap corrupted block at %p", block);
|
||||
gTestRunnerState.result = TEST_RESULT_ERROR;
|
||||
break;
|
||||
}
|
||||
|
@ -268,6 +271,15 @@ top:
|
|||
block = block->next;
|
||||
}
|
||||
while (block != head);
|
||||
|
||||
for (i = 0; i < NUM_TASKS; i++)
|
||||
{
|
||||
if (gTasks[i].isActive)
|
||||
{
|
||||
Test_MgbaPrintf("%p: task not freed", gTasks[i].func);
|
||||
gTestRunnerState.result = TEST_RESULT_FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gTestRunnerState.test->runner == &gAssumptionsRunner)
|
||||
|
@ -585,6 +597,9 @@ static s32 MgbaVPrintf_(const char *fmt, va_list va)
|
|||
p = va_arg(va, unsigned);
|
||||
{
|
||||
s32 n;
|
||||
i = MgbaPutchar_(i, '<');
|
||||
i = MgbaPutchar_(i, '0');
|
||||
i = MgbaPutchar_(i, 'x');
|
||||
for (n = 0; n < 7; n++)
|
||||
{
|
||||
unsigned nybble = (p >> (24 - (4*n))) & 0xF;
|
||||
|
@ -593,6 +608,7 @@ static s32 MgbaVPrintf_(const char *fmt, va_list va)
|
|||
else
|
||||
i = MgbaPutchar_(i, 'a' + nybble - 10);
|
||||
}
|
||||
i = MgbaPutchar_(i, '>');
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
|
|
3147
tools/mgba-rom-test-hydra/elf.h
Normal file
3147
tools/mgba-rom-test-hydra/elf.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -21,6 +21,7 @@
|
|||
#include <regex.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -32,6 +33,7 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include "elf.h"
|
||||
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
|
@ -69,10 +71,103 @@ struct Runner
|
|||
char assumeFailed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
|
||||
};
|
||||
|
||||
struct Symbol {
|
||||
const char *name;
|
||||
uint32_t address;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct SymbolTable {
|
||||
struct Symbol *symbols;
|
||||
size_t symbols_n;
|
||||
};
|
||||
|
||||
static unsigned nrunners = 0;
|
||||
static unsigned runners_digits = 0;
|
||||
static struct Runner *runners = NULL;
|
||||
|
||||
// TODO: Build the symbol table on demand.
|
||||
static struct SymbolTable symbol_table = { NULL, 0 };
|
||||
|
||||
static const struct Symbol *lookup_address(uint32_t address)
|
||||
{
|
||||
int lo = 0, hi = symbol_table.symbols_n;
|
||||
while (lo < hi)
|
||||
{
|
||||
int mi = lo + (hi - lo) / 2;
|
||||
const struct Symbol *symbol = &symbol_table.symbols[mi];
|
||||
if (address < symbol->address)
|
||||
hi = mi;
|
||||
else if (address >= symbol->address + symbol->size)
|
||||
lo = mi + 1;
|
||||
else
|
||||
return symbol;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Similar to 'fwrite(buffer, 1, size, f)' except that anything which
|
||||
// looks like the output of '%p' (i.e. '<0x\d{7}>') is translated into
|
||||
// the name of a symbol (if it represents one).
|
||||
static void fprint_buffer(FILE *f, const char *buffer, size_t size)
|
||||
{
|
||||
const char *buffer_end = buffer + size;
|
||||
while (buffer < buffer_end)
|
||||
{
|
||||
// Find the next '<0x'.
|
||||
char *buffer_ = memmem(buffer, buffer_end - buffer, "<0x", 3);
|
||||
|
||||
// No '<0x' or could not possibly match, print everything.
|
||||
if (buffer_ == NULL || buffer_end - buffer_ < 11 || buffer_[10] != '>')
|
||||
{
|
||||
fwrite(buffer, 1, buffer_end - buffer, f);
|
||||
break;
|
||||
}
|
||||
|
||||
// Print everything before the '<0x'.
|
||||
fwrite(buffer, 1, buffer_ - buffer, f);
|
||||
buffer = buffer_;
|
||||
|
||||
unsigned long address = strtoul(buffer + 3, &buffer_, 16);
|
||||
// Un-mirror EWRAM/IWRAM/ROM addresses.
|
||||
switch (address & 0xF000000)
|
||||
{
|
||||
case 0x2000000: address = address & 0x203FFFF; break;
|
||||
case 0x3000000: address = address & 0x3007FFF; break;
|
||||
case 0x7000000: address = address & 0x70003FF; break;
|
||||
case 0xA000000: address = address & 0x9FFFFFF; break;
|
||||
case 0xB000000: address = address & 0x9FFFFFF; break;
|
||||
case 0xC000000: address = address & 0x9FFFFFF; break;
|
||||
case 0xD000000: address = address & 0x9FFFFFF; break;
|
||||
}
|
||||
|
||||
// Not a 7-digit address, print the '<0x' part and loop.
|
||||
if (buffer_ != buffer + 10)
|
||||
{
|
||||
fwrite(buffer, 1, 3, f);
|
||||
buffer += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
const struct Symbol *symbol = lookup_address(address);
|
||||
|
||||
// Not a symbol, print the parsed part and loop.
|
||||
if (symbol == NULL)
|
||||
{
|
||||
fwrite(buffer, 1, 11, f);
|
||||
buffer += 11;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (symbol->address == address)
|
||||
fprintf(f, "<%s>", symbol->name);
|
||||
else
|
||||
fprintf(f, "<%s+0x%lx>", symbol->name, address - symbol->address);
|
||||
|
||||
buffer += 11;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_read(int i, struct Runner *runner)
|
||||
{
|
||||
char *sol = runner->input_buffer;
|
||||
|
@ -156,7 +251,7 @@ add_to_results:
|
|||
soc += 2;
|
||||
fprintf(stdout, "[%0*d] %s: ", runners_digits, i, runner->test_name);
|
||||
fwrite(soc, 1, eol - soc, stdout);
|
||||
fwrite(runner->output_buffer, 1, runner->output_buffer_size, stdout);
|
||||
fprint_buffer(stdout, runner->output_buffer, runner->output_buffer_size);
|
||||
strcpy(runner->test_name, "WAITING...");
|
||||
runner->output_buffer_size = 0;
|
||||
break;
|
||||
|
@ -186,11 +281,7 @@ buffer_output:
|
|||
}
|
||||
else
|
||||
{
|
||||
if (write(STDOUT_FILENO, sol, eol - sol) == -1)
|
||||
{
|
||||
perror("write failed");
|
||||
exit(2);
|
||||
}
|
||||
fwrite(sol, 1, eol - sol, stdout);
|
||||
}
|
||||
sol += n;
|
||||
consumed += n;
|
||||
|
@ -233,12 +324,80 @@ static void exit2(int _)
|
|||
exit(2);
|
||||
}
|
||||
|
||||
int compare_strings(const void * a, const void * b)
|
||||
static int compare_addresses(const void *a, const void *b)
|
||||
{
|
||||
const char *arg1 = (const char *) a;
|
||||
const char *arg2 = (const char *) b;
|
||||
const struct Symbol *sa = a, *sb = b;
|
||||
if (sa->address < sb->address)
|
||||
return -1;
|
||||
else if (sa->address == sb->address)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
return strcmp(arg1, arg2);
|
||||
static void build_symbol_table(void *elf)
|
||||
{
|
||||
if (memcmp(elf, ELFMAG, 4) != 0)
|
||||
goto error;
|
||||
|
||||
size_t symbol_table_symbols_c = 1024;
|
||||
symbol_table.symbols = malloc(symbol_table_symbols_c * sizeof(*symbol_table.symbols));
|
||||
if (symbol_table.symbols == NULL)
|
||||
goto error;
|
||||
|
||||
const Elf32_Ehdr *ehdr = (Elf32_Ehdr *)elf;
|
||||
const Elf32_Shdr *shdrs = (Elf32_Shdr *)(elf + ehdr->e_shoff);
|
||||
|
||||
if (ehdr->e_shstrndx == SHN_UNDEF)
|
||||
goto error;
|
||||
const Elf32_Shdr *shdr_shstr = &shdrs[ehdr->e_shstrndx];
|
||||
const char *shstr = (const char *)(elf + shdr_shstr->sh_offset);
|
||||
const Elf32_Shdr *shdr_symtab = NULL;
|
||||
const Elf32_Shdr *shdr_strtab = NULL;
|
||||
for (int i = 0; i < ehdr->e_shnum; i++)
|
||||
{
|
||||
const char *sh_name = shstr + shdrs[i].sh_name;
|
||||
if (strcmp(sh_name, ".symtab") == 0)
|
||||
shdr_symtab = &shdrs[i];
|
||||
else if (strcmp(sh_name, ".strtab") == 0)
|
||||
shdr_strtab = &shdrs[i];
|
||||
}
|
||||
if (!shdr_symtab)
|
||||
goto error;
|
||||
if (!shdr_strtab)
|
||||
goto error;
|
||||
|
||||
const Elf32_Sym *symtab = (Elf32_Sym *)(elf + shdr_symtab->sh_offset);
|
||||
const char *strtab = (const char *)(elf + shdr_strtab->sh_offset);
|
||||
for (int i = 0; i < shdr_symtab->sh_size / shdr_symtab->sh_entsize; i++)
|
||||
{
|
||||
if (symtab[i].st_name == 0) continue;
|
||||
if (symtab[i].st_shndx > ehdr->e_shnum) continue;
|
||||
if (symtab[i].st_value < 0x2000000 || symtab[i].st_size == 0) continue;
|
||||
struct Symbol symbol =
|
||||
{
|
||||
.name = strtab + symtab[i].st_name,
|
||||
.address = symtab[i].st_value,
|
||||
.size = symtab[i].st_size,
|
||||
};
|
||||
if (symbol_table.symbols_n == symbol_table_symbols_c)
|
||||
{
|
||||
symbol_table_symbols_c *= 2;
|
||||
void *symbols = realloc(symbol_table.symbols, symbol_table_symbols_c * sizeof(*symbol_table.symbols));
|
||||
if (symbols == NULL)
|
||||
goto error;
|
||||
symbol_table.symbols = symbols;
|
||||
}
|
||||
symbol_table.symbols[symbol_table.symbols_n++] = symbol;
|
||||
}
|
||||
|
||||
qsort(symbol_table.symbols, symbol_table.symbols_n, sizeof(*symbol_table.symbols), compare_addresses);
|
||||
return;
|
||||
|
||||
error:
|
||||
free(symbol_table.symbols);
|
||||
symbol_table.symbols = NULL;
|
||||
symbol_table.symbols_n = 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -292,6 +451,8 @@ int main(int argc, char *argv[])
|
|||
exit(2);
|
||||
}
|
||||
|
||||
build_symbol_table(elf);
|
||||
|
||||
nrunners = 1;
|
||||
const char *makeflags = getenv("MAKEFLAGS");
|
||||
if (makeflags)
|
||||
|
|
Loading…
Reference in a new issue