Multithread debugger and emulator with message queues
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 050b299..cfb97fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,6 @@
project(6502 VERSION 0.1.0 LANGUAGES C)
option(GEN_INSTRUCTIONS_HEADER ON)
-option(NO_PTHREAD OFF)
include_directories(nuklear)
@@ -15,12 +14,6 @@
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
endif()
-if (${NO_PTHREAD})
- add_definitions(-DNO_PTHREAD)
-else()
- set(THREAD pthread)
-endif()
-
add_executable(6502 main.c cpu.c cpu.h dbg.c dbg.h
instructions.h gui.h gui.c screen.h screen.c common.h common.c)
-target_link_libraries(6502 readline SDL2 GL GLU GLEW m ${THREAD})
+target_link_libraries(6502 readline SDL2 GL GLU GLEW m rt pthread)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2a6c61e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright 2020 swissChili
+
+Redistribution and use in source and binary forms, with or without modification, are permitted
+provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and
+the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+and the following disclaimer in the documentation and/or other materials provided with the
+distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 2849e4a..924f97d 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,45 @@
definitions of every 6502 opcode, its mnemonic and addressing mode.
It is built automatically by cmake.
+## Dependencies
+
+- POSIX Threads
+- POSIX Messsage Queues
+- SDL2
+- OpenGL 3
+- GLEW
+- GNU Readline
+- Nuklear (included)
+
+## License
+
+```
+Copyright 2020 swissChili
+
+Redistribution and use in source and binary forms, with or without modification, are permitted
+provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and
+the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+and the following disclaimer in the documentation and/or other materials provided with the
+distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+```
+
+---
```
____________________________________
diff --git a/common.c b/common.c
index 6772dad..7f1875f 100644
--- a/common.c
+++ b/common.c
@@ -9,7 +9,7 @@
void throw_(const char *msg, const char *file, unsigned int line)
{
- printf("\033[31mException thrown:\033[33m %s:%d\033[0m %s\n", file, line, msg);
+ fprintf(stderr, "\033[31mException thrown:\033[33m %s:%d\033[0m %s\n", file, line, msg);
for (int i = g_catch_len - 1; i >= 0; i--)
{
diff --git a/common.h b/common.h
index 6b751ba..e1e297f 100644
--- a/common.h
+++ b/common.h
@@ -10,7 +10,7 @@
#define ASSERT(message, body) \
{ \
- if (!body) \
+ if (!(body)) \
THROW("Assert failed: " message " [" #body "]"); \
}
diff --git a/cpu.c b/cpu.c
index 9d85261..8b5eda8 100644
--- a/cpu.c
+++ b/cpu.c
@@ -4,6 +4,8 @@
#define SCREEN_ONLY_SDL
#include "screen.h"
+#include "dbg.h"
+#include <errno.h>
#include <endian.h>
#include <stdio.h>
#include <stdlib.h>
@@ -539,8 +541,6 @@
void step(cpu_t *cpu)
{
- static int steps;
- steps++;
cpu->screen_dirty = false;
uint8_t pc = cpu->pc;
uint8_t op = cpu->mem[cpu->pc++];
@@ -559,17 +559,6 @@
warn("Undefined opcode %x near %x [%x]", op, pc, cpu->mem[pc]);
THROW("Undefined opcode");
}
-
- if (steps % 100 == 0)
- printf("%d\n", steps);
-
-// If can't run screen in seperate thread, just run it here (bad)
-#ifdef NO_PTHREAD
- if (g_scr)
- {
- sdl_screen(g_scr, cpu->mem + CPU_FB_ADDR, cpu->screen_dirty);
- }
-#endif
}
int dump_inst(cpu_t *cpu, char *buf, const char *mn, uint16_t addr, uint8_t am)
@@ -677,3 +666,34 @@
printf("CPU Halted\n");
}
+
+void run_mq(cpu_t *cpu, mqd_t mq)
+{
+ char buf[MQ_BUF_LEN];
+ bool running;
+
+ while (true)
+ {
+ if (running)
+ {
+ if (cpu->running)
+ step(cpu);
+ else
+ running = false;
+ }
+
+ ssize_t recvd = mq_receive(mq, buf, MQ_BUF_LEN * 2, NULL);
+
+ if (recvd == -1 && errno != EAGAIN)
+ {
+ printf("errno = %d\n", errno);
+ THROW("mq_receive returned -1");
+ }
+
+ if (recvd > 0)
+ {
+ if (debug_stmt(cpu, buf, &running))
+ break;
+ }
+ }
+}
diff --git a/cpu.h b/cpu.h
index 5b91ed4..3c82c6c 100644
--- a/cpu.h
+++ b/cpu.h
@@ -2,12 +2,16 @@
#include <stdint.h>
#include <stdbool.h>
+#include <mqueue.h>
#define REGISTERS R(A) R(X) R(Y) R(SP)
#define CPU_FB_ADDR 0x200
#define CPU_FB_W 32
#define CPU_FB_H 32
+#define MQ_BUF_LEN 512
+#define MQ_NAME "/6502-to-cpu"
+
enum // Registers
{
A, X, Y, SP
@@ -148,3 +152,4 @@
// Buffer must be freed by user
char *disas_step(cpu_t *cpu);
void run(cpu_t *cpu);
+void run_mq(cpu_t *cpu, mqd_t mq);
diff --git a/dbg.c b/dbg.c
index f5ca437..577feee 100644
--- a/dbg.c
+++ b/dbg.c
@@ -4,64 +4,103 @@
#include <readline/readline.h>
#include <readline/history.h>
#include <string.h>
+#include <pthread.h>
#include <stdlib.h>
-void debug(cpu_t *cpu)
+bool debug_stmt(cpu_t *cpu, char *input, bool *running)
{
- while (true)
+ char *tok = strtok(input, " \t\r\n\v");
+
+ if (!tok || !*tok)
+ return false;
+
+ if (!strcmp(tok, "step") || !strcmp(tok, "s"))
+ {
+ step(cpu);
+ }
+ else if (!strcmp(tok, "show") || !strcmp(tok, "print"))
+ {
+ if ((tok = strtok(NULL, " ")))
+ {
+ char *ok = 0;
+ if (tok[0] == '$')
+ {
+ uint16_t addr = strtol(tok + 1, &ok, 16);
+
+ printf("Memory:\n");
+ printf("\t$%x %x\n", addr, cpu->mem[addr]);
+ if (addr < 0xFFFF)
+ printf("\t$%x %x\n", addr + 1, cpu->mem[addr + 1]);
+ }
+ else
+ {
+ printf("Expected an address as an argument in the form "
+ "$1234, not %s\n", tok);
+ }
+ }
+ else
+ {
+ printf("Registers:\n");
+
+ printf("\tPC:\t$%x\n", cpu->pc);
+ #define R(r) printf("\t" #r ":\t$%x\n", cpu->regs[r]);
+ REGISTERS
+ #undef R
+ }
+ }
+ else if (!strcmp(tok, "run"))
+ {
+ *running = true;
+ }
+ else if (!strcmp(tok, "quit") || !strcmp(tok, "exit"))
+ {
+ printf("Bye\n");
+ return true;
+ }
+ else
+ {
+ printf("Unknown command %s\n", tok);
+ }
+
+ return false;
+}
+
+typedef struct
+{
+ mqd_t mq;
+ cpu_t *cpu;
+} debug_prompt_arg_t;
+
+void debug_prompt(debug_prompt_arg_t *arg)
+{
+ mqd_t mq = arg->mq;
+ cpu_t *cpu = arg->cpu;
+ free(arg);
+
+ bool running = true;
+ while (running)
{
char *input = readline("\033[33m> \033[0m");
if (!input || !*input)
continue;
- char *tok = strtok(input, " \t\r\n\v");
+ if (!strcmp(input, "quit") || !strcmp(input, "exit"))
+ running = false;
- if (!tok || !*tok)
- continue;
-
- if (!strcmp(tok, "step") || !strcmp(tok, "s"))
- {
- step(cpu);
- }
- else if (!strcmp(tok, "show") || !strcmp(tok, "print"))
- {
- if ((tok = strtok(NULL, " ")))
- {
- char *ok = 0;
- if (tok[0] == '$')
- {
- uint16_t addr = strtol(tok + 1, &ok, 16);
-
- printf("Memory:\n");
- printf("\t$%x %x\n", addr, cpu->mem[addr]);
- if (addr < 0xFFFF)
- printf("\t$%x %x\n", addr + 1, cpu->mem[addr + 1]);
- }
- else
- {
- printf("Expected an address as an argument in the form "
- "$1234, not %s\n", tok);
- }
- }
- else
- {
- printf("Registers:\n");
-
- #define R(r) printf("\t" #r ":\t%x\n", cpu->regs[r]);
- REGISTERS
- #undef R
- }
- }
- else if (!strcmp(tok, "quit") || !strcmp(tok, "exit"))
- {
- printf("Bye\n");
- return;
- }
- else
- {
- printf("Unknown command %s\n", tok);
- }
+ mq_send(mq, input, strlen(input) + 1, 2);
add_history(input);
+ free(input);
}
}
+
+pthread_t start_debug_prompt(mqd_t mq, cpu_t *cpu)
+{
+ debug_prompt_arg_t *arg = malloc(sizeof(debug_prompt_arg_t));
+ arg->mq = mq;
+ arg->cpu = cpu;
+
+ pthread_t thread;
+ pthread_create(&thread, NULL, (void *(*)(void *))&debug_prompt, arg);
+ return thread;
+}
diff --git a/dbg.h b/dbg.h
index c31c1c3..67f082d 100644
--- a/dbg.h
+++ b/dbg.h
@@ -2,4 +2,9 @@
#include "cpu.h"
-void debug(cpu_t *cpu);
+#include <stdbool.h>
+#include <mqueue.h>
+
+bool debug_stmt(cpu_t *cpu, char *input, bool *running);
+// void debug_prompt(mqd_t mq, cpu_t *cpu);
+pthread_t start_debug_prompt(mqd_t mq, cpu_t *cpu);
\ No newline at end of file
diff --git a/gui.c b/gui.c
index 2b5799a..7a9841f 100644
--- a/gui.c
+++ b/gui.c
@@ -23,8 +23,19 @@
#define MAX_VERTEX_MEMORY 512 * 1024
#define MAX_ELEMENT_MEMORY 128 * 1024
-void gui(cpu_t *cpu)
+typedef struct
{
+ cpu_t *cpu;
+ mqd_t mq;
+} gui_arg_t;
+
+void gui(gui_arg_t *arg)
+{
+ cpu_t *cpu = arg->cpu;
+ mqd_t mq = arg->mq;
+
+ free(arg);
+
SDL_Window *win;
SDL_GLContext glContext;
int win_width, win_height;
@@ -223,3 +234,13 @@
SDL_DestroyWindow(win);
SDL_Quit();
}
+
+
+void start_gui(mqd_t mq, cpu_t *cpu)
+{
+ pthread_t gui_thread;
+ gui_arg_t *arg = malloc(sizeof(gui_arg_t));
+ arg->cpu = cpu;
+ arg->mq = mq;
+ pthread_create(&gui_thread, NULL, (void *(*)(void *))&gui, arg);
+}
diff --git a/gui.h b/gui.h
index 730db11..cba3baa 100644
--- a/gui.h
+++ b/gui.h
@@ -2,4 +2,5 @@
#include "cpu.h"
-void gui(cpu_t *cpu);
+// void gui(cpu_t *cpu);
+void start_gui(mqd_t mq_to_cpu, cpu_t *cpu);
diff --git a/main.c b/main.c
index 3b1da46..779096c 100644
--- a/main.c
+++ b/main.c
@@ -6,23 +6,26 @@
#include <bits/getopt_core.h>
#include <ctype.h>
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
-
-extern sdl_screen_t *g_scr;
-
-#ifndef NO_PTHREAD
#include <pthread.h>
+#include <mqueue.h>
+#include <sys/stat.h>
void cleanup_screen_thread(pthread_t thread)
{
- g_screen_thread_halt = true;
puts("Cleaning up screen...");
- pthread_join(thread, NULL);
+ pthread_cancel(thread);
}
-#endif
+
+void cleanup_debug_prompt_thread(pthread_t thread)
+{
+ puts("Cleaning up debug prompt...");
+ pthread_cancel(thread);
+}
int main(int argc, char **argv)
{
@@ -97,11 +100,28 @@
}
cpu_t cpu;
+ mqd_t mq_to_cpu;
+
+ struct mq_attr attrs;
+ attrs.mq_maxmsg = 10;
+ attrs.mq_msgsize = MQ_BUF_LEN;
if (should_read)
{
cpu = new_cpu();
fread(cpu.mem + 0x600, 0xFFFF - 0x600, 1, input);
+
+ int unlink = mq_unlink(MQ_NAME);
+ if (unlink < 0 && errno != ENOENT)
+ {
+ printf("Warning: mq_unlink() error: %d %s\n", errno, strerror(errno));
+ }
+
+ mq_to_cpu = mq_open(MQ_NAME, O_RDWR | O_CREAT | O_NONBLOCK, S_IWUSR|S_IRUSR, &attrs);
+ printf("error after mq_open (%ld) = %d %s\n", attrs.mq_msgsize, errno, strerror(errno));
+ ASSERT("Open message queue for emulator", mq_to_cpu > 0)
+
+ mq_send(mq_to_cpu, "init", 5, 2);
}
else
{
@@ -111,12 +131,7 @@
if (scrflag)
{
-#ifndef NO_PTHREAD
CATCH(&cleanup_screen_thread, start_screen_thread(cpu.mem + CPU_FB_ADDR));
-#else
- sdl_screen_t scr = new_sdl_screen(8);
- g_scr = &scr;
-#endif
}
if (guiflag && scrflag)
@@ -126,11 +141,12 @@
if (guiflag)
{
- gui(&cpu);
+ start_gui(mq_to_cpu, &cpu);
+ run_mq(&cpu, mq_to_cpu);
}
else if (disflag)
{
- disas_num(&cpu, 12);
+ disas_num(&cpu, 64);
}
else if (runflag)
{
@@ -143,14 +159,13 @@
}
else if (debugflag)
{
- debug(&cpu);
+ CATCH(&cleanup_debug_prompt_thread, start_debug_prompt(mq_to_cpu, &cpu));
+ run_mq(&cpu, mq_to_cpu);
}
-
-#ifdef NO_PTHREAD
- if (scrflag)
- free_sdl_screen(g_scr);
-#endif
if (should_read)
+ {
free_cpu(&cpu);
+ mq_close(mq_to_cpu);
+ }
}
diff --git a/screen.c b/screen.c
index e19baa2..4b01adc 100644
--- a/screen.c
+++ b/screen.c
@@ -3,10 +3,8 @@
#include "common.h"
#include <SDL2/SDL.h>
-
-#ifndef NO_PTHREAD
#include <pthread.h>
-#endif
+
struct nk_color byte_to_color(uint8_t b)
{
@@ -101,25 +99,23 @@
return false;
}
-
-#ifndef NO_PTHREAD
-
bool g_screen_thread_halt = false;
-
void *screen_thread(uint8_t *mem)
{
sdl_screen_t scr = new_sdl_screen(8);
+
+ pthread_cleanup_push(((void (*)(void *))&free_sdl_screen), ((void *)&scr));
+
while (true)
{
if (sdl_screen(&scr, mem, true) || g_screen_thread_halt)
break;
}
- free_sdl_screen(&scr);
- exit(0);
+ pthread_cleanup_pop(true);
- return NULL;
+ pthread_exit(NULL);
}
pthread_t start_screen_thread(uint8_t *mem)
@@ -128,5 +124,3 @@
pthread_create(&thread, NULL, (void *(*)(void *))&screen_thread, mem);
return thread;
}
-
-#endif
diff --git a/screen.h b/screen.h
index 9eeddc8..da3dd44 100644
--- a/screen.h
+++ b/screen.h
@@ -2,6 +2,7 @@
#include <stdint.h>
#include <stdbool.h>
+#include <pthread.h>
#ifndef SCREEN_ONLY_SDL
@@ -35,13 +36,7 @@
void free_sdl_screen(sdl_screen_t *scr);
bool sdl_screen(sdl_screen_t *scr, uint8_t *mem, bool dirty);
-#ifndef NO_PTHREAD
-
-#include <pthread.h>
-
extern bool g_screen_thread_halt;
void *screen_thread(uint8_t *mem);
pthread_t start_screen_thread(uint8_t *mem);
-
-#endif