From 70afc343c75ecc8895b9308ee6a15fcc7a3b0f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20VINCENT?= Date: Fri, 26 Aug 2011 10:56:50 +0200 Subject: linux-user: Fix initialization of the heap contents when allocating new pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Technically the new mmapped pages are already initialized to zero since they are anonymous, however we have to take care with the contents that come from the remaining part of the previous page: it may contains garbage data due to a previous heap usage (grown then shrunken). This patch completes commit 55f08c84. The problem could be reproduced when emulating the build process of Perl 5.12.3 on ARMedSlack 13.37: make[1]: Entering directory `/tmp/perl-5.12.3/cpan/Compress-Raw-Bzip2' cc -c -I. -fno-strict-aliasing -pipe -fstack-protector \ -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ -O2 -DVERSION=\"2.024\" -DXS_VERSION=\"2.024\" -fPIC "-I../.." \ -DBZ_NO_STDIO decompress.c decompress.c: In function 'BZ2_decompress': decompress.c:621:1: internal compiler error: Segmentation fault Signed-off-by: Riku Voipio Signed-off-by: Laurent ALFONSI Signed-off-by: Cédric VINCENT --- linux-user/syscall.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'linux-user') diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 6bdf4e6ab4..ae08c9e538 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -797,6 +797,15 @@ abi_long do_brk(abi_ulong new_brk) MAP_ANON|MAP_PRIVATE, 0, 0)); if (mapped_addr == brk_page) { + /* Heap contents are initialized to zero, as for anonymous + * mapped pages. Technically the new pages are already + * initialized to zero since they *are* anonymous mapped + * pages, however we have to take care with the contents that + * come from the remaining part of the previous page: it may + * contains garbage data due to a previous heap usage (grown + * then shrunken). */ + memset(g2h(target_brk), 0, brk_page - target_brk); + target_brk = new_brk; brk_page = HOST_PAGE_ALIGN(target_brk); DEBUGF_BRK("%#010x (mapped_addr == brk_page)\n", target_brk); -- cgit v1.2.3 From 97cc75606aef406e90a243cdb25347039003e7f0 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Wed, 31 Aug 2011 17:24:34 +0100 Subject: linux-user: Implement new ARM 64 bit cmpxchg kernel helper linux-user: Implement new ARM 64 bit cmpxchg kernel helper Linux 3.1 will have a new kernel-page helper for ARM implementing 64 bit cmpxchg. Implement this helper in QEMU linux-user mode: * Provide kernel helper emulation for 64bit cmpxchg * Allow guest to object to guest offset to ensure it can map a page * Populate page with kernel helper version Signed-off-by: Riku Voipio Reviewed-by: Peter Maydell Signed-off-by: Dr. David Alan Gilbert --- linux-user/elfload.c | 56 +++++++++++++++++++++++++++++++-- linux-user/main.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++- linux-user/qemu.h | 6 ++++ 3 files changed, 148 insertions(+), 3 deletions(-) (limited to 'linux-user') diff --git a/linux-user/elfload.c b/linux-user/elfload.c index 04e8e6e065..8677bba0d8 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -332,6 +332,49 @@ enum ARM_HWCAP_ARM_VFPv3D16 = 1 << 13, }; +#define TARGET_HAS_GUEST_VALIDATE_BASE +/* We want the opportunity to check the suggested base */ +bool guest_validate_base(unsigned long guest_base) +{ + unsigned long real_start, test_page_addr; + + /* We need to check that we can force a fault on access to the + * commpage at 0xffff0fxx + */ + test_page_addr = guest_base + (0xffff0f00 & qemu_host_page_mask); + /* Note it needs to be writeable to let us initialise it */ + real_start = (unsigned long) + mmap((void *)test_page_addr, qemu_host_page_size, + PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + /* If we can't map it then try another address */ + if (real_start == -1ul) { + return 0; + } + + if (real_start != test_page_addr) { + /* OS didn't put the page where we asked - unmap and reject */ + munmap((void *)real_start, qemu_host_page_size); + return 0; + } + + /* Leave the page mapped + * Populate it (mmap should have left it all 0'd) + */ + + /* Kernel helper versions */ + __put_user(5, (uint32_t *)g2h(0xffff0ffcul)); + + /* Now it's populated make it RO */ + if (mprotect((void *)test_page_addr, qemu_host_page_size, PROT_READ)) { + perror("Protecting guest commpage"); + exit(-1); + } + + return 1; /* All good */ +} + #define ELF_HWCAP (ARM_HWCAP_ARM_SWP | ARM_HWCAP_ARM_HALF \ | ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \ | ARM_HWCAP_ARM_FPA | ARM_HWCAP_ARM_VFP \ @@ -1309,6 +1352,14 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc, return sp; } +#ifndef TARGET_HAS_GUEST_VALIDATE_BASE +/* If the guest doesn't have a validation function just agree */ +bool guest_validate_base(unsigned long guest_base) +{ + return 1; +} +#endif + static void probe_guest_base(const char *image_name, abi_ulong loaddr, abi_ulong hiaddr) { @@ -1345,7 +1396,9 @@ static void probe_guest_base(const char *image_name, if (real_start == (unsigned long)-1) { goto exit_perror; } - if (real_start == host_start) { + guest_base = real_start - loaddr; + if ((real_start == host_start) && + guest_validate_base(guest_base)) { break; } /* That address didn't work. Unmap and try a different one. @@ -1368,7 +1421,6 @@ static void probe_guest_base(const char *image_name, qemu_log("Relocating guest address space from 0x" TARGET_ABI_FMT_lx " to 0x%lx\n", loaddr, real_start); - guest_base = real_start - loaddr; } return; diff --git a/linux-user/main.c b/linux-user/main.c index 89a51d76cd..25cb4ddf36 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -456,6 +456,83 @@ void cpu_loop(CPUX86State *env) #ifdef TARGET_ARM +/* + * See the Linux kernel's Documentation/arm/kernel_user_helpers.txt + * Input: + * r0 = pointer to oldval + * r1 = pointer to newval + * r2 = pointer to target value + * + * Output: + * r0 = 0 if *ptr was changed, non-0 if no exchange happened + * C set if *ptr was changed, clear if no exchange happened + * + * Note segv's in kernel helpers are a bit tricky, we can set the + * data address sensibly but the PC address is just the entry point. + */ +static void arm_kernel_cmpxchg64_helper(CPUARMState *env) +{ + uint64_t oldval, newval, val; + uint32_t addr, cpsr; + target_siginfo_t info; + + /* Based on the 32 bit code in do_kernel_trap */ + + /* XXX: This only works between threads, not between processes. + It's probably possible to implement this with native host + operations. However things like ldrex/strex are much harder so + there's not much point trying. */ + start_exclusive(); + cpsr = cpsr_read(env); + addr = env->regs[2]; + + if (get_user_u64(oldval, env->regs[0])) { + env->cp15.c6_data = env->regs[0]; + goto segv; + }; + + if (get_user_u64(newval, env->regs[1])) { + env->cp15.c6_data = env->regs[1]; + goto segv; + }; + + if (get_user_u64(val, addr)) { + env->cp15.c6_data = addr; + goto segv; + } + + if (val == oldval) { + val = newval; + + if (put_user_u64(val, addr)) { + env->cp15.c6_data = addr; + goto segv; + }; + + env->regs[0] = 0; + cpsr |= CPSR_C; + } else { + env->regs[0] = -1; + cpsr &= ~CPSR_C; + } + cpsr_write(env, cpsr, CPSR_C); + end_exclusive(); + return; + +segv: + end_exclusive(); + /* We get the PC of the entry address - which is as good as anything, + on a real kernel what you get depends on which mode it uses. */ + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* XXX: check env->error_code */ + info.si_code = TARGET_SEGV_MAPERR; + info._sifields._sigfault._addr = env->cp15.c6_data; + queue_signal(env, info.si_signo, &info); + + end_exclusive(); +} + /* Handle a jump to the kernel code page. */ static int do_kernel_trap(CPUARMState *env) @@ -495,6 +572,10 @@ do_kernel_trap(CPUARMState *env) case 0xffff0fe0: /* __kernel_get_tls */ env->regs[0] = env->cp15.c13_tls2; break; + case 0xffff0f60: /* __kernel_cmpxchg64 */ + arm_kernel_cmpxchg64_helper(env); + break; + default: return 1; } @@ -752,7 +833,6 @@ void cpu_loop(CPUARMState *env) goto do_segv; case EXCP_DATA_ABORT: addr = env->cp15.c6_data; - goto do_segv; do_segv: { info.si_signo = SIGSEGV; @@ -3180,6 +3260,13 @@ int main(int argc, char **argv, char **envp) } qemu_log("Reserved 0x%lx bytes of guest address space\n", reserved_va); } + + if (reserved_va || have_guest_base) { + if (!guest_validate_base(guest_base)) { + fprintf(stderr, "Guest base/Reserved VA rejected by guest code\n"); + exit(1); + } + } #endif /* CONFIG_USE_GUEST_BASE */ /* diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 627c8b3423..55ad9d8586 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -202,6 +202,12 @@ int get_osversion(void); void fork_start(void); void fork_end(int child); +/* Return true if the proposed guest_base is suitable for the guest. + * The guest code may leave a page mapped and populate it if the + * address is suitable. + */ +bool guest_validate_base(unsigned long guest_base); + #include "qemu-log.h" /* strace.c */ -- cgit v1.2.3 From ff7a981affbbc4f19f6f92143a3ddff90a1363fe Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 6 Sep 2011 14:15:50 +0100 Subject: linux-user: Exit with an error if we couldn't set up gdbserver If gdbserver_start() fails (usually because we couldn't bind to the requested TCP port) then exit qemu rather than blithely continuing. This brings the linux-user behaviour in to line with system mode. Signed-off-by: Riku Voipio Signed-off-by: Peter Maydell --- linux-user/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'linux-user') diff --git a/linux-user/main.c b/linux-user/main.c index 25cb4ddf36..8910d2c494 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -3655,7 +3655,11 @@ int main(int argc, char **argv, char **envp) #endif if (gdbstub_port) { - gdbserver_start (gdbstub_port); + if (gdbserver_start(gdbstub_port) < 0) { + fprintf(stderr, "qemu: could not open gdbserver on port %d\n", + gdbstub_port); + exit(1); + } gdb_handlesig(env, 0); } cpu_loop(env); -- cgit v1.2.3 From 29fb0f25303a76edb100362eaea59b6f74bdb823 Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Tue, 9 Aug 2011 12:31:41 -0700 Subject: linux-user: Fix MIPS indirect syscall handling Change the number of argument for MIPS sys_syscall from 0 to 8. This allows arguments for indirect syscalls to be processed correctly. Signed-off-by: Riku Voipio Reviewed-by: Peter Maydell Signed-off-by: An-Cheng Huang --- linux-user/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'linux-user') diff --git a/linux-user/main.c b/linux-user/main.c index 8910d2c494..3df91f388a 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -1749,7 +1749,7 @@ void cpu_loop(CPUPPCState *env) #define MIPS_SYS(name, args) args, static const uint8_t mips_syscall_args[] = { - MIPS_SYS(sys_syscall , 0) /* 4000 */ + MIPS_SYS(sys_syscall , 8) /* 4000 */ MIPS_SYS(sys_exit , 1) MIPS_SYS(sys_fork , 0) MIPS_SYS(sys_read , 3) -- cgit v1.2.3 From 94c19610a6973ad917d9c154eabfc2ee27bc4f59 Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Tue, 9 Aug 2011 12:32:38 -0700 Subject: linux-user: Verify MIPS syscall arguments On MIPS, some syscall arguments are taken from the stack. This patch adds verification such that do_syscall() is only invoked if all arguments have been successfully taken from the stack. Signed-off-by: Riku Voipio Reviewed-by: Peter Maydell Signed-off-by: An-Cheng Huang --- linux-user/main.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'linux-user') diff --git a/linux-user/main.c b/linux-user/main.c index 3df91f388a..0cc9148a80 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -2170,11 +2170,22 @@ void cpu_loop(CPUMIPSState *env) sp_reg = env->active_tc.gpr[29]; switch (nb_args) { /* these arguments are taken from the stack */ - /* FIXME - what to do if get_user() fails? */ - case 8: get_user_ual(arg8, sp_reg + 28); - case 7: get_user_ual(arg7, sp_reg + 24); - case 6: get_user_ual(arg6, sp_reg + 20); - case 5: get_user_ual(arg5, sp_reg + 16); + case 8: + if ((ret = get_user_ual(arg8, sp_reg + 28)) != 0) { + goto done_syscall; + } + case 7: + if ((ret = get_user_ual(arg7, sp_reg + 24)) != 0) { + goto done_syscall; + } + case 6: + if ((ret = get_user_ual(arg6, sp_reg + 20)) != 0) { + goto done_syscall; + } + case 5: + if ((ret = get_user_ual(arg5, sp_reg + 16)) != 0) { + goto done_syscall; + } default: break; } @@ -2185,6 +2196,7 @@ void cpu_loop(CPUMIPSState *env) env->active_tc.gpr[7], arg5, arg6, arg7, arg8); } +done_syscall: if (ret == -TARGET_QEMU_ESIGRETURN) { /* Returning from a successful sigreturn syscall. Avoid clobbering register state. */ -- cgit v1.2.3 From a790ae389bf779424789dc6ddc946ea268d58c42 Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Tue, 9 Aug 2011 12:34:06 -0700 Subject: linux-user: Implement setxattr/getxattr/removexattr syscalls This patch implements the setxattr, getxattr, and removexattr syscalls if CONFIG_ATTR is enabled. Note that since libattr uses indirect syscalls for these, this change depends on the fix for indirect syscall handling on MIPS. Signed-off-by: Riku Voipio Reviewed-by: Peter Maydell Signed-off-by: An-Cheng Huang --- linux-user/syscall.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) (limited to 'linux-user') diff --git a/linux-user/syscall.c b/linux-user/syscall.c index ae08c9e538..39a34db4b1 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -70,6 +70,9 @@ int __clone2(int (*fn)(void *), void *child_stack_base, #ifdef CONFIG_EPOLL #include #endif +#ifdef CONFIG_ATTR +#include +#endif #define termios host_termios #define winsize host_winsize @@ -7642,22 +7645,67 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, #endif break; #endif +#ifdef CONFIG_ATTR #ifdef TARGET_NR_setxattr - case TARGET_NR_setxattr: case TARGET_NR_lsetxattr: case TARGET_NR_fsetxattr: - case TARGET_NR_getxattr: case TARGET_NR_lgetxattr: case TARGET_NR_fgetxattr: case TARGET_NR_listxattr: case TARGET_NR_llistxattr: case TARGET_NR_flistxattr: - case TARGET_NR_removexattr: case TARGET_NR_lremovexattr: case TARGET_NR_fremovexattr: ret = -TARGET_EOPNOTSUPP; break; + case TARGET_NR_setxattr: + { + void *p, *n, *v; + p = lock_user_string(arg1); + n = lock_user_string(arg2); + v = lock_user(VERIFY_READ, arg3, arg4, 1); + if (p && n && v) { + ret = get_errno(setxattr(p, n, v, arg4, arg5)); + } else { + ret = -TARGET_EFAULT; + } + unlock_user(p, arg1, 0); + unlock_user(n, arg2, 0); + unlock_user(v, arg3, 0); + } + break; + case TARGET_NR_getxattr: + { + void *p, *n, *v; + p = lock_user_string(arg1); + n = lock_user_string(arg2); + v = lock_user(VERIFY_WRITE, arg3, arg4, 0); + if (p && n && v) { + ret = get_errno(getxattr(p, n, v, arg4)); + } else { + ret = -TARGET_EFAULT; + } + unlock_user(p, arg1, 0); + unlock_user(n, arg2, 0); + unlock_user(v, arg3, arg4); + } + break; + case TARGET_NR_removexattr: + { + void *p, *n; + p = lock_user_string(arg1); + n = lock_user_string(arg2); + if (p && n) { + ret = get_errno(removexattr(p, n)); + } else { + ret = -TARGET_EFAULT; + } + unlock_user(p, arg1, 0); + unlock_user(n, arg2, 0); + } + break; #endif +#endif /* CONFIG_ATTR */ #ifdef TARGET_NR_set_thread_area case TARGET_NR_set_thread_area: #if defined(TARGET_MIPS) -- cgit v1.2.3 From fc9c54124d134dbd76338a92a91804dab2df8166 Mon Sep 17 00:00:00 2001 From: Johannes Schauer Date: Sat, 6 Aug 2011 08:54:12 +0200 Subject: introduce environment variables for all qemu-user options (Edits by Riku Voipio to apply to current HEAD) Rework option parsing code for linux-user in a table-driven manner to allow environment variables for all commandline options. Also generate usage() output from option table. Fix complains from checkpatch.pl, also have envlist global Signed-off-by: Johannes Schauer Signed-off-by: Riku Voipio --- linux-user/main.c | 555 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 348 insertions(+), 207 deletions(-) (limited to 'linux-user') diff --git a/linux-user/main.c b/linux-user/main.c index 0cc9148a80..186358bd63 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -39,6 +39,11 @@ char *exec_path; int singlestep; +const char *filename; +const char *argv0; +int gdbstub_port; +envlist_t *envlist; +const char *cpu_model; unsigned long mmap_min_addr; #if defined(CONFIG_USE_GUEST_BASE) unsigned long guest_base; @@ -46,6 +51,8 @@ int have_guest_base; unsigned long reserved_va; #endif +static void usage(void); + static const char *interp_prefix = CONFIG_QEMU_INTERP_PREFIX; const char *qemu_uname_release = CONFIG_UNAME_RELEASE; @@ -2879,57 +2886,6 @@ void cpu_loop(CPUS390XState *env) #endif /* TARGET_S390X */ -static void version(void) -{ - printf("qemu-" TARGET_ARCH " version " QEMU_VERSION QEMU_PKGVERSION - ", Copyright (c) 2003-2008 Fabrice Bellard\n"); -} - -static void usage(void) -{ - version(); - printf("usage: qemu-" TARGET_ARCH " [options] program [arguments...]\n" - "Linux CPU emulator (compiled for %s emulation)\n" - "\n" - "Standard options:\n" - "-h print this help\n" - "-version display version information and exit\n" - "-g port wait gdb connection to port\n" - "-L path set the elf interpreter prefix (default=%s)\n" - "-s size set the stack size in bytes (default=%ld)\n" - "-cpu model select CPU (-cpu ? for list)\n" - "-drop-ld-preload drop LD_PRELOAD for target process\n" - "-E var=value sets/modifies targets environment variable(s)\n" - "-U var unsets targets environment variable(s)\n" - "-0 argv0 forces target process argv[0] to be argv0\n" -#if defined(CONFIG_USE_GUEST_BASE) - "-B address set guest_base address to address\n" - "-R size reserve size bytes for guest virtual address space\n" -#endif - "\n" - "Debug options:\n" - "-d options activate log (logfile=%s)\n" - "-p pagesize set the host page size to 'pagesize'\n" - "-singlestep always run in singlestep mode\n" - "-strace log system calls\n" - "\n" - "Environment variables:\n" - "QEMU_STRACE Print system calls and arguments similar to the\n" - " 'strace' program. Enable by setting to any value.\n" - "You can use -E and -U options to set/unset environment variables\n" - "for target process. It is possible to provide several variables\n" - "by repeating the option. For example:\n" - " -E var1=val2 -E var2=val2 -U LD_PRELOAD -U LD_DEBUG\n" - "Note that if you provide several changes to single variable\n" - "last change will stay in effect.\n" - , - TARGET_ARCH, - interp_prefix, - guest_stack_size, - DEBUG_LOGFILE); - exit(1); -} - THREAD CPUState *thread_env; void task_settid(TaskState *ts) @@ -2965,26 +2921,358 @@ void init_task_state(TaskState *ts) } ts->sigqueue_table[i].next = NULL; } - + +static void handle_arg_help(const char *arg) +{ + usage(); +} + +static void handle_arg_log(const char *arg) +{ + int mask; + const CPULogItem *item; + + mask = cpu_str_to_log_mask(arg); + if (!mask) { + printf("Log items (comma separated):\n"); + for (item = cpu_log_items; item->mask != 0; item++) { + printf("%-10s %s\n", item->name, item->help); + } + exit(1); + } + cpu_set_log(mask); +} + +static void handle_arg_set_env(const char *arg) +{ + char *r, *p, *token; + r = p = strdup(arg); + while ((token = strsep(&p, ",")) != NULL) { + if (envlist_setenv(envlist, token) != 0) { + usage(); + } + } + free(r); +} + +static void handle_arg_unset_env(const char *arg) +{ + char *r, *p, *token; + r = p = strdup(arg); + while ((token = strsep(&p, ",")) != NULL) { + if (envlist_unsetenv(envlist, token) != 0) { + usage(); + } + } + free(r); +} + +static void handle_arg_argv0(const char *arg) +{ + argv0 = strdup(arg); +} + +static void handle_arg_stack_size(const char *arg) +{ + char *p; + guest_stack_size = strtoul(arg, &p, 0); + if (guest_stack_size == 0) { + usage(); + } + + if (*p == 'M') { + guest_stack_size *= 1024 * 1024; + } else if (*p == 'k' || *p == 'K') { + guest_stack_size *= 1024; + } +} + +static void handle_arg_ld_prefix(const char *arg) +{ + interp_prefix = strdup(arg); +} + +static void handle_arg_pagesize(const char *arg) +{ + qemu_host_page_size = atoi(arg); + if (qemu_host_page_size == 0 || + (qemu_host_page_size & (qemu_host_page_size - 1)) != 0) { + fprintf(stderr, "page size must be a power of two\n"); + exit(1); + } +} + +static void handle_arg_gdb(const char *arg) +{ + gdbstub_port = atoi(arg); +} + +static void handle_arg_uname(const char *arg) +{ + qemu_uname_release = strdup(arg); +} + +static void handle_arg_cpu(const char *arg) +{ + cpu_model = strdup(arg); + if (cpu_model == NULL || strcmp(cpu_model, "?") == 0) { + /* XXX: implement xxx_cpu_list for targets that still miss it */ +#if defined(cpu_list_id) + cpu_list_id(stdout, &fprintf, ""); +#elif defined(cpu_list) + cpu_list(stdout, &fprintf); /* deprecated */ +#endif + exit(1); + } +} + +#if defined(CONFIG_USE_GUEST_BASE) +static void handle_arg_guest_base(const char *arg) +{ + guest_base = strtol(arg, NULL, 0); + have_guest_base = 1; +} + +static void handle_arg_reserved_va(const char *arg) +{ + char *p; + int shift = 0; + reserved_va = strtoul(arg, &p, 0); + switch (*p) { + case 'k': + case 'K': + shift = 10; + break; + case 'M': + shift = 20; + break; + case 'G': + shift = 30; + break; + } + if (shift) { + unsigned long unshifted = reserved_va; + p++; + reserved_va <<= shift; + if (((reserved_va >> shift) != unshifted) +#if HOST_LONG_BITS > TARGET_VIRT_ADDR_SPACE_BITS + || (reserved_va > (1ul << TARGET_VIRT_ADDR_SPACE_BITS)) +#endif + ) { + fprintf(stderr, "Reserved virtual address too big\n"); + exit(1); + } + } + if (*p) { + fprintf(stderr, "Unrecognised -R size suffix '%s'\n", p); + exit(1); + } +} +#endif + +static void handle_arg_singlestep(const char *arg) +{ + singlestep = 1; +} + +static void handle_arg_strace(const char *arg) +{ + do_strace = 1; +} + +static void handle_arg_version(const char *arg) +{ + printf("qemu-" TARGET_ARCH " version " QEMU_VERSION QEMU_PKGVERSION + ", Copyright (c) 2003-2008 Fabrice Bellard\n"); +} + +struct qemu_argument { + const char *argv; + const char *env; + bool has_arg; + void (*handle_opt)(const char *arg); + const char *example; + const char *help; +}; + +struct qemu_argument arg_table[] = { + {"h", "", false, handle_arg_help, + "", "print this help"}, + {"g", "QEMU_GDB", true, handle_arg_gdb, + "port", "wait gdb connection to 'port'"}, + {"L", "QEMU_LD_PREFIX", true, handle_arg_ld_prefix, + "path", "set the elf interpreter prefix to 'path'"}, + {"s", "QEMU_STACK_SIZE", true, handle_arg_stack_size, + "size", "set the stack size to 'size' bytes"}, + {"cpu", "QEMU_CPU", true, handle_arg_cpu, + "model", "select CPU (-cpu ? for list)"}, + {"E", "QEMU_SET_ENV", true, handle_arg_set_env, + "var=value", "sets targets environment variable (see below)"}, + {"U", "QEMU_UNSET_ENV", true, handle_arg_unset_env, + "var", "unsets targets environment variable (see below)"}, + {"0", "QEMU_ARGV0", true, handle_arg_argv0, + "argv0", "forces target process argv[0] to be 'argv0'"}, + {"r", "QEMU_UNAME", true, handle_arg_uname, + "uname", "set qemu uname release string to 'uname'"}, +#if defined(CONFIG_USE_GUEST_BASE) + {"B", "QEMU_GUEST_BASE", true, handle_arg_guest_base, + "address", "set guest_base address to 'address'"}, + {"R", "QEMU_RESERVED_VA", true, handle_arg_reserved_va, + "size", "reserve 'size' bytes for guest virtual address space"}, +#endif + {"d", "QEMU_LOG", true, handle_arg_log, + "options", "activate log"}, + {"p", "QEMU_PAGESIZE", true, handle_arg_pagesize, + "pagesize", "set the host page size to 'pagesize'"}, + {"singlestep", "QEMU_SINGLESTEP", false, handle_arg_singlestep, + "", "run in singlestep mode"}, + {"strace", "QEMU_STRACE", false, handle_arg_strace, + "", "log system calls"}, + {"version", "QEMU_VERSION", false, handle_arg_version, + "", "log system calls"}, + {NULL, NULL, false, NULL, NULL, NULL} +}; + +static void usage(void) +{ + struct qemu_argument *arginfo; + int maxarglen; + int maxenvlen; + + printf("usage: qemu-" TARGET_ARCH " [options] program [arguments...]\n" + "Linux CPU emulator (compiled for " TARGET_ARCH " emulation)\n" + "\n" + "Options and associated environment variables:\n" + "\n"); + + maxarglen = maxenvlen = 0; + + for (arginfo = arg_table; arginfo->handle_opt != NULL; arginfo++) { + if (strlen(arginfo->env) > maxenvlen) { + maxenvlen = strlen(arginfo->env); + } + if (strlen(arginfo->argv) > maxarglen) { + maxarglen = strlen(arginfo->argv); + } + } + + printf("%-*s%-*sDescription\n", maxarglen+3, "Argument", + maxenvlen+1, "Env-variable"); + + for (arginfo = arg_table; arginfo->handle_opt != NULL; arginfo++) { + if (arginfo->has_arg) { + printf("-%s %-*s %-*s %s\n", arginfo->argv, + (int)(maxarglen-strlen(arginfo->argv)), arginfo->example, + maxenvlen, arginfo->env, arginfo->help); + } else { + printf("-%-*s %-*s %s\n", maxarglen+1, arginfo->argv, + maxenvlen, arginfo->env, + arginfo->help); + } + } + + printf("\n" + "Defaults:\n" + "QEMU_LD_PREFIX = %s\n" + "QEMU_STACK_SIZE = %ld byte\n" + "QEMU_LOG = %s\n", + interp_prefix, + guest_stack_size, + DEBUG_LOGFILE); + + printf("\n" + "You can use -E and -U options or the QEMU_SET_ENV and\n" + "QEMU_UNSET_ENV environment variables to set and unset\n" + "environment variables for the target process.\n" + "It is possible to provide several variables by separating them\n" + "by commas in getsubopt(3) style. Additionally it is possible to\n" + "provide the -E and -U options multiple times.\n" + "The following lines are equivalent:\n" + " -E var1=val2 -E var2=val2 -U LD_PRELOAD -U LD_DEBUG\n" + " -E var1=val2,var2=val2 -U LD_PRELOAD,LD_DEBUG\n" + " QEMU_SET_ENV=var1=val2,var2=val2 QEMU_UNSET_ENV=LD_PRELOAD,LD_DEBUG\n" + "Note that if you provide several changes to a single variable\n" + "the last change will stay in effect.\n"); + + exit(1); +} + +static int parse_args(int argc, char **argv) +{ + const char *r; + int optind; + struct qemu_argument *arginfo; + + for (arginfo = arg_table; arginfo->handle_opt != NULL; arginfo++) { + if (arginfo->env == NULL) { + continue; + } + + r = getenv(arginfo->env); + if (r != NULL) { + arginfo->handle_opt(r); + } + } + + optind = 1; + for (;;) { + if (optind >= argc) { + break; + } + r = argv[optind]; + if (r[0] != '-') { + break; + } + optind++; + r++; + if (!strcmp(r, "-")) { + break; + } + + for (arginfo = arg_table; arginfo->handle_opt != NULL; arginfo++) { + if (!strcmp(r, arginfo->argv)) { + if (optind >= argc) { + usage(); + } + + arginfo->handle_opt(argv[optind]); + + if (arginfo->has_arg) { + optind++; + } + + break; + } + } + + /* no option matched the current argv */ + if (arginfo->handle_opt == NULL) { + usage(); + } + } + + if (optind >= argc) { + usage(); + } + + filename = argv[optind]; + exec_path = argv[optind]; + + return optind; +} + int main(int argc, char **argv, char **envp) { - const char *filename; - const char *cpu_model; const char *log_file = DEBUG_LOGFILE; - const char *log_mask = NULL; struct target_pt_regs regs1, *regs = ®s1; struct image_info info1, *info = &info1; struct linux_binprm bprm; TaskState *ts; CPUState *env; int optind; - const char *r; - int gdbstub_port = 0; char **target_environ, **wrk; char **target_argv; int target_argc; - envlist_t *envlist = NULL; - const char *argv0 = NULL; int i; int ret; @@ -3019,156 +3307,9 @@ int main(int argc, char **argv, char **envp) cpudef_setup(); /* parse cpu definitions in target config file (TBD) */ #endif - optind = 1; - for(;;) { - if (optind >= argc) - break; - r = argv[optind]; - if (r[0] != '-') - break; - optind++; - r++; - if (!strcmp(r, "-")) { - break; - } else if (!strcmp(r, "d")) { - if (optind >= argc) { - break; - } - log_mask = argv[optind++]; - } else if (!strcmp(r, "D")) { - if (optind >= argc) { - break; - } - log_file = argv[optind++]; - } else if (!strcmp(r, "E")) { - r = argv[optind++]; - if (envlist_setenv(envlist, r) != 0) - usage(); - } else if (!strcmp(r, "ignore-environment")) { - envlist_free(envlist); - if ((envlist = envlist_create()) == NULL) { - (void) fprintf(stderr, "Unable to allocate envlist\n"); - exit(1); - } - } else if (!strcmp(r, "U")) { - r = argv[optind++]; - if (envlist_unsetenv(envlist, r) != 0) - usage(); - } else if (!strcmp(r, "0")) { - r = argv[optind++]; - argv0 = r; - } else if (!strcmp(r, "s")) { - if (optind >= argc) - break; - r = argv[optind++]; - guest_stack_size = strtoul(r, (char **)&r, 0); - if (guest_stack_size == 0) - usage(); - if (*r == 'M') - guest_stack_size *= 1024 * 1024; - else if (*r == 'k' || *r == 'K') - guest_stack_size *= 1024; - } else if (!strcmp(r, "L")) { - interp_prefix = argv[optind++]; - } else if (!strcmp(r, "p")) { - if (optind >= argc) - break; - qemu_host_page_size = atoi(argv[optind++]); - if (qemu_host_page_size == 0 || - (qemu_host_page_size & (qemu_host_page_size - 1)) != 0) { - fprintf(stderr, "page size must be a power of two\n"); - exit(1); - } - } else if (!strcmp(r, "g")) { - if (optind >= argc) - break; - gdbstub_port = atoi(argv[optind++]); - } else if (!strcmp(r, "r")) { - qemu_uname_release = argv[optind++]; - } else if (!strcmp(r, "cpu")) { - cpu_model = argv[optind++]; - if (cpu_model == NULL || strcmp(cpu_model, "?") == 0) { -/* XXX: implement xxx_cpu_list for targets that still miss it */ -#if defined(cpu_list_id) - cpu_list_id(stdout, &fprintf, ""); -#elif defined(cpu_list) - cpu_list(stdout, &fprintf); /* deprecated */ -#endif - exit(1); - } -#if defined(CONFIG_USE_GUEST_BASE) - } else if (!strcmp(r, "B")) { - guest_base = strtol(argv[optind++], NULL, 0); - have_guest_base = 1; - } else if (!strcmp(r, "R")) { - char *p; - int shift = 0; - reserved_va = strtoul(argv[optind++], &p, 0); - switch (*p) { - case 'k': - case 'K': - shift = 10; - break; - case 'M': - shift = 20; - break; - case 'G': - shift = 30; - break; - } - if (shift) { - unsigned long unshifted = reserved_va; - p++; - reserved_va <<= shift; - if (((reserved_va >> shift) != unshifted) -#if HOST_LONG_BITS > TARGET_VIRT_ADDR_SPACE_BITS - || (reserved_va > (1ul << TARGET_VIRT_ADDR_SPACE_BITS)) -#endif - ) { - fprintf(stderr, "Reserved virtual address too big\n"); - exit(1); - } - } - if (*p) { - fprintf(stderr, "Unrecognised -R size suffix '%s'\n", p); - exit(1); - } -#endif - } else if (!strcmp(r, "drop-ld-preload")) { - (void) envlist_unsetenv(envlist, "LD_PRELOAD"); - } else if (!strcmp(r, "singlestep")) { - singlestep = 1; - } else if (!strcmp(r, "strace")) { - do_strace = 1; - } else if (!strcmp(r, "version")) { - version(); - exit(0); - } else { - usage(); - } - } /* init debug */ cpu_set_log_filename(log_file); - if (log_mask) { - int mask; - const CPULogItem *item; - - mask = cpu_str_to_log_mask(log_mask); - if (!mask) { - printf("Log items (comma separated):\n"); - for (item = cpu_log_items; item->mask != 0; item++) { - printf("%-10s %s\n", item->name, item->help); - } - exit(1); - } - cpu_set_log(mask); - } - - if (optind >= argc) { - usage(); - } - filename = argv[optind]; - exec_path = argv[optind]; + optind = parse_args(argc, argv); /* Zero out regs */ memset(regs, 0, sizeof(struct target_pt_regs)); -- cgit v1.2.3