Commit a2d1ebaf890da03de850812cc8dbec2d56efb4e8
1 parent
4046d913
GDB hosted syscalls.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2364 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
3 changed files
with
262 additions
and
32 deletions
arm-semi.c
| ... | ... | @@ -61,7 +61,30 @@ |
| 61 | 61 | #define O_BINARY 0 |
| 62 | 62 | #endif |
| 63 | 63 | |
| 64 | -int open_modeflags[12] = { | |
| 64 | +#define GDB_O_RDONLY 0x000 | |
| 65 | +#define GDB_O_WRONLY 0x001 | |
| 66 | +#define GDB_O_RDWR 0x002 | |
| 67 | +#define GDB_O_APPEND 0x008 | |
| 68 | +#define GDB_O_CREAT 0x200 | |
| 69 | +#define GDB_O_TRUNC 0x400 | |
| 70 | +#define GDB_O_BINARY 0 | |
| 71 | + | |
| 72 | +static int gdb_open_modeflags[12] = { | |
| 73 | + GDB_O_RDONLY, | |
| 74 | + GDB_O_RDONLY | GDB_O_BINARY, | |
| 75 | + GDB_O_RDWR, | |
| 76 | + GDB_O_RDWR | GDB_O_BINARY, | |
| 77 | + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, | |
| 78 | + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, | |
| 79 | + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, | |
| 80 | + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, | |
| 81 | + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, | |
| 82 | + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, | |
| 83 | + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, | |
| 84 | + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY | |
| 85 | +}; | |
| 86 | + | |
| 87 | +static int open_modeflags[12] = { | |
| 65 | 88 | O_RDONLY, |
| 66 | 89 | O_RDONLY | O_BINARY, |
| 67 | 90 | O_RDWR, |
| ... | ... | @@ -142,6 +165,35 @@ static void softmmu_unlock_user(CPUState *env, void *p, target_ulong addr, |
| 142 | 165 | #define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len) |
| 143 | 166 | #endif |
| 144 | 167 | |
| 168 | +static target_ulong arm_semi_syscall_len; | |
| 169 | + | |
| 170 | +static void arm_semi_cb(CPUState *env, target_ulong ret, target_ulong err) | |
| 171 | +{ | |
| 172 | +#ifdef CONFIG_USER_ONLY | |
| 173 | + TaskState *ts = env->opaque; | |
| 174 | +#endif | |
| 175 | + if (ret == (target_ulong)-1) { | |
| 176 | +#ifdef CONFIG_USER_ONLY | |
| 177 | + ts->swi_errno = err; | |
| 178 | +#endif | |
| 179 | + env->regs[0] = ret; | |
| 180 | + } else { | |
| 181 | + /* Fixup syscalls that use nonstardard return conventions. */ | |
| 182 | + switch (env->regs[0]) { | |
| 183 | + case SYS_WRITE: | |
| 184 | + case SYS_READ: | |
| 185 | + env->regs[0] = arm_semi_syscall_len - ret; | |
| 186 | + break; | |
| 187 | + case SYS_SEEK: | |
| 188 | + env->regs[0] = 0; | |
| 189 | + break; | |
| 190 | + default: | |
| 191 | + env->regs[0] = ret; | |
| 192 | + break; | |
| 193 | + } | |
| 194 | + } | |
| 195 | +} | |
| 196 | + | |
| 145 | 197 | #define ARG(n) tget32(args + (n) * 4) |
| 146 | 198 | #define SET_ARG(n, val) tput32(args + (n) * 4,val) |
| 147 | 199 | uint32_t do_arm_semihosting(CPUState *env) |
| ... | ... | @@ -170,52 +222,99 @@ uint32_t do_arm_semihosting(CPUState *env) |
| 170 | 222 | else |
| 171 | 223 | return STDOUT_FILENO; |
| 172 | 224 | } |
| 173 | - ret = set_swi_errno(ts, open(s, open_modeflags[ARG(1)], 0644)); | |
| 225 | + if (use_gdb_syscalls()) { | |
| 226 | + gdb_do_syscall(arm_semi_cb, "open,%s,%x,1a4", ARG(0), (int)ARG(2), | |
| 227 | + gdb_open_modeflags[ARG(1)]); | |
| 228 | + return env->regs[0]; | |
| 229 | + } else { | |
| 230 | + ret = set_swi_errno(ts, open(s, open_modeflags[ARG(1)], 0644)); | |
| 231 | + } | |
| 174 | 232 | unlock_user(s, ARG(0), 0); |
| 175 | 233 | return ret; |
| 176 | 234 | case SYS_CLOSE: |
| 177 | - return set_swi_errno(ts, close(ARG(0))); | |
| 235 | + if (use_gdb_syscalls()) { | |
| 236 | + gdb_do_syscall(arm_semi_cb, "close,%x", ARG(0)); | |
| 237 | + return env->regs[0]; | |
| 238 | + } else { | |
| 239 | + return set_swi_errno(ts, close(ARG(0))); | |
| 240 | + } | |
| 178 | 241 | case SYS_WRITEC: |
| 179 | 242 | { |
| 180 | 243 | char c = tget8(args); |
| 181 | 244 | /* Write to debug console. stderr is near enough. */ |
| 182 | - return write(STDERR_FILENO, &c, 1); | |
| 245 | + if (use_gdb_syscalls()) { | |
| 246 | + gdb_do_syscall(arm_semi_cb, "write,2,%x,1", args); | |
| 247 | + return env->regs[0]; | |
| 248 | + } else { | |
| 249 | + return write(STDERR_FILENO, &c, 1); | |
| 250 | + } | |
| 183 | 251 | } |
| 184 | 252 | case SYS_WRITE0: |
| 185 | 253 | s = lock_user_string(args); |
| 186 | - ret = write(STDERR_FILENO, s, strlen(s)); | |
| 254 | + len = strlen(s); | |
| 255 | + if (use_gdb_syscalls()) { | |
| 256 | + gdb_do_syscall(arm_semi_cb, "write,2,%x,%x\n", args, len); | |
| 257 | + ret = env->regs[0]; | |
| 258 | + } else { | |
| 259 | + ret = write(STDERR_FILENO, s, len); | |
| 260 | + } | |
| 187 | 261 | unlock_user(s, args, 0); |
| 188 | 262 | return ret; |
| 189 | 263 | case SYS_WRITE: |
| 190 | 264 | len = ARG(2); |
| 191 | - s = lock_user(ARG(1), len, 1); | |
| 192 | - ret = set_swi_errno(ts, write(ARG(0), s, len)); | |
| 193 | - unlock_user(s, ARG(1), 0); | |
| 194 | - if (ret == (uint32_t)-1) | |
| 195 | - return -1; | |
| 196 | - return ARG(2) - ret; | |
| 265 | + if (use_gdb_syscalls()) { | |
| 266 | + arm_semi_syscall_len = len; | |
| 267 | + gdb_do_syscall(arm_semi_cb, "write,%x,%x,%x", ARG(0), ARG(1), len); | |
| 268 | + return env->regs[0]; | |
| 269 | + } else { | |
| 270 | + s = lock_user(ARG(1), len, 1); | |
| 271 | + ret = set_swi_errno(ts, write(ARG(0), s, len)); | |
| 272 | + unlock_user(s, ARG(1), 0); | |
| 273 | + if (ret == (uint32_t)-1) | |
| 274 | + return -1; | |
| 275 | + return len - ret; | |
| 276 | + } | |
| 197 | 277 | case SYS_READ: |
| 198 | 278 | len = ARG(2); |
| 199 | - s = lock_user(ARG(1), len, 0); | |
| 200 | - do | |
| 201 | - ret = set_swi_errno(ts, read(ARG(0), s, len)); | |
| 202 | - while (ret == -1 && errno == EINTR); | |
| 203 | - unlock_user(s, ARG(1), len); | |
| 204 | - if (ret == (uint32_t)-1) | |
| 205 | - return -1; | |
| 206 | - return ARG(2) - ret; | |
| 279 | + if (use_gdb_syscalls()) { | |
| 280 | + arm_semi_syscall_len = len; | |
| 281 | + gdb_do_syscall(arm_semi_cb, "read,%x,%x,%x", ARG(0), ARG(1), len); | |
| 282 | + return env->regs[0]; | |
| 283 | + } else { | |
| 284 | + s = lock_user(ARG(1), len, 0); | |
| 285 | + do | |
| 286 | + ret = set_swi_errno(ts, read(ARG(0), s, len)); | |
| 287 | + while (ret == -1 && errno == EINTR); | |
| 288 | + unlock_user(s, ARG(1), len); | |
| 289 | + if (ret == (uint32_t)-1) | |
| 290 | + return -1; | |
| 291 | + return len - ret; | |
| 292 | + } | |
| 207 | 293 | case SYS_READC: |
| 208 | 294 | /* XXX: Read from debug cosole. Not implemented. */ |
| 209 | 295 | return 0; |
| 210 | 296 | case SYS_ISTTY: |
| 211 | - return isatty(ARG(0)); | |
| 297 | + if (use_gdb_syscalls()) { | |
| 298 | + gdb_do_syscall(arm_semi_cb, "isatty,%x", ARG(0)); | |
| 299 | + return env->regs[0]; | |
| 300 | + } else { | |
| 301 | + return isatty(ARG(0)); | |
| 302 | + } | |
| 212 | 303 | case SYS_SEEK: |
| 213 | - ret = set_swi_errno(ts, lseek(ARG(0), ARG(1), SEEK_SET)); | |
| 214 | - if (ret == (uint32_t)-1) | |
| 215 | - return -1; | |
| 216 | - return 0; | |
| 304 | + if (use_gdb_syscalls()) { | |
| 305 | + gdb_do_syscall(arm_semi_cb, "fseek,%x,%x,0", ARG(0), ARG(1)); | |
| 306 | + return env->regs[0]; | |
| 307 | + } else { | |
| 308 | + ret = set_swi_errno(ts, lseek(ARG(0), ARG(1), SEEK_SET)); | |
| 309 | + if (ret == (uint32_t)-1) | |
| 310 | + return -1; | |
| 311 | + return 0; | |
| 312 | + } | |
| 217 | 313 | case SYS_FLEN: |
| 218 | - { | |
| 314 | + if (use_gdb_syscalls()) { | |
| 315 | + /* TODO: Use stat syscall. */ | |
| 316 | + return -1; | |
| 317 | + } else { | |
| 219 | 318 | struct stat buf; |
| 220 | 319 | ret = set_swi_errno(ts, fstat(ARG(0), &buf)); |
| 221 | 320 | if (ret == (uint32_t)-1) |
| ... | ... | @@ -226,12 +325,21 @@ uint32_t do_arm_semihosting(CPUState *env) |
| 226 | 325 | /* XXX: Not implemented. */ |
| 227 | 326 | return -1; |
| 228 | 327 | case SYS_REMOVE: |
| 229 | - s = lock_user_string(ARG(0)); | |
| 230 | - ret = set_swi_errno(ts, remove(s)); | |
| 231 | - unlock_user(s, ARG(0), 0); | |
| 328 | + if (use_gdb_syscalls()) { | |
| 329 | + gdb_do_syscall(arm_semi_cb, "unlink,%s", ARG(0), (int)ARG(1)); | |
| 330 | + ret = env->regs[0]; | |
| 331 | + } else { | |
| 332 | + s = lock_user_string(ARG(0)); | |
| 333 | + ret = set_swi_errno(ts, remove(s)); | |
| 334 | + unlock_user(s, ARG(0), 0); | |
| 335 | + } | |
| 232 | 336 | return ret; |
| 233 | 337 | case SYS_RENAME: |
| 234 | - { | |
| 338 | + if (use_gdb_syscalls()) { | |
| 339 | + gdb_do_syscall(arm_semi_cb, "rename,%s,%s", | |
| 340 | + ARG(0), (int)ARG(1), ARG(2), (int)ARG(3)); | |
| 341 | + return env->regs[0]; | |
| 342 | + } else { | |
| 235 | 343 | char *s2; |
| 236 | 344 | s = lock_user_string(ARG(0)); |
| 237 | 345 | s2 = lock_user_string(ARG(2)); |
| ... | ... | @@ -245,9 +353,14 @@ uint32_t do_arm_semihosting(CPUState *env) |
| 245 | 353 | case SYS_TIME: |
| 246 | 354 | return set_swi_errno(ts, time(NULL)); |
| 247 | 355 | case SYS_SYSTEM: |
| 248 | - s = lock_user_string(ARG(0)); | |
| 249 | - ret = set_swi_errno(ts, system(s)); | |
| 250 | - unlock_user(s, ARG(0), 0); | |
| 356 | + if (use_gdb_syscalls()) { | |
| 357 | + gdb_do_syscall(arm_semi_cb, "system,%s", ARG(0), (int)ARG(1)); | |
| 358 | + return env->regs[0]; | |
| 359 | + } else { | |
| 360 | + s = lock_user_string(ARG(0)); | |
| 361 | + ret = set_swi_errno(ts, system(s)); | |
| 362 | + unlock_user(s, ARG(0), 0); | |
| 363 | + } | |
| 251 | 364 | case SYS_ERRNO: |
| 252 | 365 | #ifdef CONFIG_USER_ONLY |
| 253 | 366 | return ts->swi_errno; | ... | ... |
gdbstub.c
| ... | ... | @@ -52,6 +52,7 @@ enum RSState { |
| 52 | 52 | RS_GETLINE, |
| 53 | 53 | RS_CHKSUM1, |
| 54 | 54 | RS_CHKSUM2, |
| 55 | + RS_SYSCALL, | |
| 55 | 56 | }; |
| 56 | 57 | typedef struct GDBState { |
| 57 | 58 | CPUState *env; /* current CPU */ |
| ... | ... | @@ -96,6 +97,27 @@ static int get_char(GDBState *s) |
| 96 | 97 | } |
| 97 | 98 | #endif |
| 98 | 99 | |
| 100 | +/* GDB stub state for use by semihosting syscalls. */ | |
| 101 | +static GDBState *gdb_syscall_state; | |
| 102 | +static gdb_syscall_complete_cb gdb_current_syscall_cb; | |
| 103 | + | |
| 104 | +enum { | |
| 105 | + GDB_SYS_UNKNOWN, | |
| 106 | + GDB_SYS_ENABLED, | |
| 107 | + GDB_SYS_DISABLED, | |
| 108 | +} gdb_syscall_mode; | |
| 109 | + | |
| 110 | +/* If gdb is connected when the first semihosting syscall occurs then use | |
| 111 | + remote gdb syscalls. Otherwise use native file IO. */ | |
| 112 | +int use_gdb_syscalls(void) | |
| 113 | +{ | |
| 114 | + if (gdb_syscall_mode == GDB_SYS_UNKNOWN) { | |
| 115 | + gdb_syscall_mode = (gdb_syscall_state ? GDB_SYS_ENABLED | |
| 116 | + : GDB_SYS_DISABLED); | |
| 117 | + } | |
| 118 | + return gdb_syscall_mode == GDB_SYS_ENABLED; | |
| 119 | +} | |
| 120 | + | |
| 99 | 121 | static void put_buffer(GDBState *s, const uint8_t *buf, int len) |
| 100 | 122 | { |
| 101 | 123 | #ifdef CONFIG_USER_ONLY |
| ... | ... | @@ -755,6 +777,34 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) |
| 755 | 777 | vm_start(); |
| 756 | 778 | #endif |
| 757 | 779 | return RS_IDLE; |
| 780 | + case 'F': | |
| 781 | + { | |
| 782 | + target_ulong ret; | |
| 783 | + target_ulong err; | |
| 784 | + | |
| 785 | + ret = strtoull(p, (char **)&p, 16); | |
| 786 | + if (*p == ',') { | |
| 787 | + p++; | |
| 788 | + err = strtoull(p, (char **)&p, 16); | |
| 789 | + } else { | |
| 790 | + err = 0; | |
| 791 | + } | |
| 792 | + if (*p == ',') | |
| 793 | + p++; | |
| 794 | + type = *p; | |
| 795 | + if (gdb_current_syscall_cb) | |
| 796 | + gdb_current_syscall_cb(s->env, ret, err); | |
| 797 | + if (type == 'C') { | |
| 798 | + put_packet(s, "T02"); | |
| 799 | + } else { | |
| 800 | +#ifdef CONFIG_USER_ONLY | |
| 801 | + s->running_state = 1; | |
| 802 | +#else | |
| 803 | + vm_start(); | |
| 804 | +#endif | |
| 805 | + } | |
| 806 | + } | |
| 807 | + break; | |
| 758 | 808 | case 'g': |
| 759 | 809 | reg_size = cpu_gdb_read_registers(env, mem_buf); |
| 760 | 810 | memtohex(buf, mem_buf, reg_size); |
| ... | ... | @@ -855,6 +905,9 @@ static void gdb_vm_stopped(void *opaque, int reason) |
| 855 | 905 | char buf[256]; |
| 856 | 906 | int ret; |
| 857 | 907 | |
| 908 | + if (s->state == RS_SYSCALL) | |
| 909 | + return; | |
| 910 | + | |
| 858 | 911 | /* disable single step if it was enable */ |
| 859 | 912 | cpu_single_step(s->env, 0); |
| 860 | 913 | |
| ... | ... | @@ -871,6 +924,60 @@ static void gdb_vm_stopped(void *opaque, int reason) |
| 871 | 924 | } |
| 872 | 925 | #endif |
| 873 | 926 | |
| 927 | +/* Send a gdb syscall request. | |
| 928 | + This accepts limited printf-style format specifiers, specifically: | |
| 929 | + %x - target_ulong argument printed in hex. | |
| 930 | + %s - string pointer (target_ulong) and length (int) pair. */ | |
| 931 | +void gdb_do_syscall(gdb_syscall_complete_cb cb, char *fmt, ...) | |
| 932 | +{ | |
| 933 | + va_list va; | |
| 934 | + char buf[256]; | |
| 935 | + char *p; | |
| 936 | + target_ulong addr; | |
| 937 | + GDBState *s; | |
| 938 | + | |
| 939 | + s = gdb_syscall_state; | |
| 940 | + if (!s) | |
| 941 | + return; | |
| 942 | + gdb_current_syscall_cb = cb; | |
| 943 | + s->state = RS_SYSCALL; | |
| 944 | +#ifndef CONFIG_USER_ONLY | |
| 945 | + vm_stop(EXCP_DEBUG); | |
| 946 | +#endif | |
| 947 | + s->state = RS_IDLE; | |
| 948 | + va_start(va, fmt); | |
| 949 | + p = buf; | |
| 950 | + *(p++) = 'F'; | |
| 951 | + while (*fmt) { | |
| 952 | + if (*fmt == '%') { | |
| 953 | + fmt++; | |
| 954 | + switch (*fmt++) { | |
| 955 | + case 'x': | |
| 956 | + addr = va_arg(va, target_ulong); | |
| 957 | + p += sprintf(p, TARGET_FMT_lx, addr); | |
| 958 | + break; | |
| 959 | + case 's': | |
| 960 | + addr = va_arg(va, target_ulong); | |
| 961 | + p += sprintf(p, TARGET_FMT_lx "/%x", addr, va_arg(va, int)); | |
| 962 | + break; | |
| 963 | + default: | |
| 964 | + fprintf(stderr, "gdbstub: Bad syscall format string '%s'\n", | |
| 965 | + fmt - 1); | |
| 966 | + break; | |
| 967 | + } | |
| 968 | + } else { | |
| 969 | + *(p++) = *(fmt++); | |
| 970 | + } | |
| 971 | + } | |
| 972 | + va_end(va); | |
| 973 | + put_packet(s, buf); | |
| 974 | +#ifdef CONFIG_USER_ONLY | |
| 975 | + gdb_handlesig(s->env, 0); | |
| 976 | +#else | |
| 977 | + cpu_interrupt(s->env, CPU_INTERRUPT_EXIT); | |
| 978 | +#endif | |
| 979 | +} | |
| 980 | + | |
| 874 | 981 | static void gdb_read_byte(GDBState *s, int ch) |
| 875 | 982 | { |
| 876 | 983 | CPUState *env = s->env; |
| ... | ... | @@ -942,6 +1049,8 @@ static void gdb_read_byte(GDBState *s, int ch) |
| 942 | 1049 | s->state = gdb_handle_packet(s, env, s->line_buf); |
| 943 | 1050 | } |
| 944 | 1051 | break; |
| 1052 | + default: | |
| 1053 | + abort(); | |
| 945 | 1054 | } |
| 946 | 1055 | } |
| 947 | 1056 | } |
| ... | ... | @@ -1034,6 +1143,8 @@ static void gdb_accept(void *opaque) |
| 1034 | 1143 | s->env = first_cpu; /* XXX: allow to change CPU */ |
| 1035 | 1144 | s->fd = fd; |
| 1036 | 1145 | |
| 1146 | + gdb_syscall_state = s; | |
| 1147 | + | |
| 1037 | 1148 | fcntl(fd, F_SETFL, O_NONBLOCK); |
| 1038 | 1149 | } |
| 1039 | 1150 | |
| ... | ... | @@ -1098,6 +1209,7 @@ static void gdb_chr_event(void *opaque, int event) |
| 1098 | 1209 | switch (event) { |
| 1099 | 1210 | case CHR_EVENT_RESET: |
| 1100 | 1211 | vm_stop(EXCP_INTERRUPT); |
| 1212 | + gdb_syscall_state = opaque; | |
| 1101 | 1213 | break; |
| 1102 | 1214 | default: |
| 1103 | 1215 | break; | ... | ... |
gdbstub.h
| ... | ... | @@ -3,6 +3,11 @@ |
| 3 | 3 | |
| 4 | 4 | #define DEFAULT_GDBSTUB_PORT 1234 |
| 5 | 5 | |
| 6 | +typedef void (*gdb_syscall_complete_cb)(CPUState *env, | |
| 7 | + target_ulong ret, target_ulong err); | |
| 8 | + | |
| 9 | +void gdb_do_syscall(gdb_syscall_complete_cb cb, char *fmt, ...); | |
| 10 | +int use_gdb_syscalls(void); | |
| 6 | 11 | #ifdef CONFIG_USER_ONLY |
| 7 | 12 | int gdb_handlesig (CPUState *, int); |
| 8 | 13 | void gdb_exit(CPUState *, int); | ... | ... |