src/platform/test/fuzz-main.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "gba/supervisor/thread.h"
7#include "gba/supervisor/config.h"
8#include "gba/gba.h"
9#include "gba/renderers/video-software.h"
10#include "gba/serialize.h"
11
12#include "platform/commandline.h"
13#include "util/string.h"
14#include "util/vfs.h"
15
16#include <errno.h>
17#include <signal.h>
18
19#define FUZZ_OPTIONS "F:NO:S:V:"
20#define FUZZ_USAGE \
21 "\nAdditional options:\n" \
22 " -F FRAMES Run for the specified number of FRAMES before exiting\n" \
23 " -N Disable video rendering entirely\n" \
24 " -O OFFSET Offset to apply savestate overlay\n" \
25 " -S FILE Load a savestate when starting the test\n" \
26 " -V FILE Overlay a second savestate over the loaded savestate\n" \
27
28struct FuzzOpts {
29 bool noVideo;
30 unsigned frames;
31 size_t overlayOffset;
32 char* savestate;
33 char* ssOverlay;
34};
35
36static void _GBAFuzzRunloop(struct GBAThread* context, unsigned frames);
37static void _GBAFuzzShutdown(int signal);
38static bool _parseFuzzOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg);
39static void _loadSavestate(struct GBAThread* context);
40
41static struct GBAThread* _thread;
42static bool _dispatchExiting = false;
43static struct VFile* _savestate = 0;
44static struct VFile* _savestateOverlay = 0;
45static size_t _overlayOffset;
46
47int main(int argc, char** argv) {
48 signal(SIGINT, _GBAFuzzShutdown);
49
50
51 struct FuzzOpts fuzzOpts = { false, 0, 0, 0, 0 };
52 struct SubParser subparser = {
53 .usage = FUZZ_USAGE,
54 .parse = _parseFuzzOpts,
55 .extraOptions = FUZZ_OPTIONS,
56 .opts = &fuzzOpts
57 };
58
59 struct GBAConfig config;
60 GBAConfigInit(&config, "fuzz");
61 GBAConfigLoad(&config);
62
63 struct GBAOptions opts = {
64 .idleOptimization = IDLE_LOOP_DETECT
65 };
66 GBAConfigLoadDefaults(&config, &opts);
67
68 struct GBAArguments args;
69 bool parsed = parseArguments(&args, &config, argc, argv, &subparser);
70 if (!parsed || args.showHelp) {
71 usage(argv[0], FUZZ_USAGE);
72 freeArguments(&args);
73 GBAConfigFreeOpts(&opts);
74 GBAConfigDeinit(&config);
75 return !parsed;
76 }
77
78 struct GBAVideoSoftwareRenderer renderer;
79 renderer.outputBuffer = 0;
80
81 struct GBAThread context = {};
82 _thread = &context;
83
84 if (!fuzzOpts.noVideo) {
85 GBAVideoSoftwareRendererCreate(&renderer);
86 renderer.outputBuffer = malloc(256 * 256 * 4);
87 renderer.outputBufferStride = 256;
88 context.renderer = &renderer.d;
89 }
90 if (fuzzOpts.savestate) {
91 _savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY);
92 free(fuzzOpts.savestate);
93 }
94 if (fuzzOpts.ssOverlay) {
95 _overlayOffset = fuzzOpts.overlayOffset;
96 if (_overlayOffset < sizeof(struct GBASerializedState)) {
97 _savestateOverlay = VFileOpen(fuzzOpts.ssOverlay, O_RDONLY);
98 }
99 free(fuzzOpts.ssOverlay);
100 }
101 if (_savestate) {
102 context.startCallback = _loadSavestate;
103 }
104
105 context.debugger = createDebugger(&args, &context);
106 context.overrides = GBAConfigGetOverrides(&config);
107
108 GBAConfigMap(&config, &opts);
109 opts.audioSync = false;
110 opts.videoSync = false;
111 GBAMapArgumentsToContext(&args, &context);
112 GBAMapOptionsToContext(&opts, &context);
113
114 int didStart = GBAThreadStart(&context);
115
116 if (!didStart) {
117 goto cleanup;
118 }
119 GBAThreadInterrupt(&context);
120 if (GBAThreadHasCrashed(&context)) {
121 GBAThreadJoin(&context);
122 goto cleanup;
123 }
124
125 GBAThreadContinue(&context);
126
127 _GBAFuzzRunloop(&context, fuzzOpts.frames);
128 GBAThreadJoin(&context);
129
130cleanup:
131 if (_savestate) {
132 _savestate->close(_savestate);
133 }
134 if (_savestateOverlay) {
135 _savestateOverlay->close(_savestateOverlay);
136 }
137 GBAConfigFreeOpts(&opts);
138 freeArguments(&args);
139 GBAConfigDeinit(&config);
140 free(context.debugger);
141 if (renderer.outputBuffer) {
142 free(renderer.outputBuffer);
143 }
144
145 return !didStart || GBAThreadHasCrashed(&context);
146}
147
148static void _GBAFuzzRunloop(struct GBAThread* context, unsigned duration) {
149 unsigned frames = 0;
150 while (context->state < THREAD_EXITING) {
151 if (GBASyncWaitFrameStart(&context->sync, 0)) {
152 ++frames;
153 }
154 GBASyncWaitFrameEnd(&context->sync);
155 if (frames >= duration) {
156 _GBAFuzzShutdown(0);
157 }
158 if (_dispatchExiting) {
159 GBAThreadEnd(context);
160 }
161 }
162}
163
164static void _GBAFuzzShutdown(int signal) {
165 UNUSED(signal);
166 // This will come in ON the GBA thread, so we have to handle it carefully
167 _dispatchExiting = true;
168 ConditionWake(&_thread->sync.videoFrameAvailableCond);
169}
170
171static bool _parseFuzzOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
172 UNUSED(config);
173 struct FuzzOpts* opts = parser->opts;
174 errno = 0;
175 switch (option) {
176 case 'F':
177 opts->frames = strtoul(arg, 0, 10);
178 return !errno;
179 case 'N':
180 opts->noVideo = true;
181 return true;
182 case 'O':
183 opts->overlayOffset = strtoul(arg, 0, 10);
184 return !errno;
185 case 'S':
186 opts->savestate = strdup(arg);
187 return true;
188 case 'V':
189 opts->ssOverlay = strdup(arg);
190 return true;
191 default:
192 return false;
193 }
194}
195
196static void _loadSavestate(struct GBAThread* context) {
197 if (!_savestateOverlay) {
198 GBALoadStateNamed(context->gba, _savestate);
199 } else {
200 struct GBASerializedState* state = GBAAllocateState();
201 _savestate->read(_savestate, state, sizeof(*state));
202 _savestateOverlay->read(_savestateOverlay, (uint8_t*) state + _overlayOffset, sizeof(*state) - _overlayOffset);
203 GBADeserialize(context->gba, state);
204 GBADeallocateState(state);
205 _savestateOverlay->close(_savestateOverlay);
206 _savestateOverlay = 0;
207 }
208 _savestate->close(_savestate);
209 _savestate = 0;
210}