| /* Simulator option handling. | |
| Copyright (C) 1996-2026 Free Software Foundation, Inc. | |
| Contributed by Cygnus Support. | |
| This file is part of GDB, the GNU debugger. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
| /* This must come before any other includes. */ | |
| #include "defs.h" | |
| #include <ctype.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include "bfd.h" | |
| #include "environ.h" | |
| #include "hashtab.h" | |
| #include "libiberty.h" | |
| #include "sim-main.h" | |
| #include "sim-options.h" | |
| #include "sim-io.h" | |
| #include "sim-assert.h" | |
| #include "version.h" | |
| /* Add a set of options to the simulator. | |
| TABLE is an array of OPTIONS terminated by a NULL `opt.name' entry. | |
| This is intended to be called by modules in their `install' handler. */ | |
| SIM_RC | |
| sim_add_option_table (SIM_DESC sd, sim_cpu *cpu, const OPTION *table) | |
| { | |
| struct option_list *ol = ((struct option_list *) | |
| xmalloc (sizeof (struct option_list))); | |
| /* Note: The list is constructed in the reverse order we're called so | |
| later calls will override earlier ones (in case that ever happens). | |
| This is the intended behaviour. */ | |
| if (cpu) | |
| { | |
| ol->next = CPU_OPTIONS (cpu); | |
| ol->options = table; | |
| CPU_OPTIONS (cpu) = ol; | |
| } | |
| else | |
| { | |
| ol->next = STATE_OPTIONS (sd); | |
| ol->options = table; | |
| STATE_OPTIONS (sd) = ol; | |
| } | |
| return SIM_RC_OK; | |
| } | |
| /* Standard option table. | |
| Modules may specify additional ones. | |
| The caller of sim_parse_args may also specify additional options | |
| by calling sim_add_option_table first. */ | |
| static DECLARE_OPTION_HANDLER (standard_option_handler); | |
| /* FIXME: We shouldn't print in --help output options that aren't usable. | |
| Some fine tuning will be necessary. One can either move less general | |
| options to another table or use a HAVE_FOO macro to ifdef out unavailable | |
| options. */ | |
| /* ??? One might want to conditionally compile out the entries that | |
| aren't enabled. There's a distinction, however, between options a | |
| simulator can't support and options that haven't been configured in. | |
| Certainly options a simulator can't support shouldn't appear in the | |
| output of --help. Whether the same thing applies to options that haven't | |
| been configured in or not isn't something I can get worked up over. | |
| [Note that conditionally compiling them out might simply involve moving | |
| the option to another table.] | |
| If you decide to conditionally compile them out as well, delete this | |
| comment and add a comment saying that that is the rule. */ | |
| typedef enum { | |
| OPTION_DEBUG_INSN = OPTION_START, | |
| OPTION_DEBUG_FILE, | |
| OPTION_DO_COMMAND, | |
| OPTION_ARCHITECTURE, | |
| OPTION_TARGET, | |
| OPTION_TARGET_INFO, | |
| OPTION_ARCHITECTURE_INFO, | |
| OPTION_ENVIRONMENT, | |
| OPTION_ALIGNMENT, | |
| OPTION_VERBOSE, | |
| OPTION_ENDIAN, | |
| OPTION_DEBUG, | |
| OPTION_HELP, | |
| OPTION_VERSION, | |
| OPTION_LOAD_LMA, | |
| OPTION_LOAD_VMA, | |
| OPTION_SYSROOT, | |
| OPTION_ARGV0, | |
| OPTION_ENV_SET, | |
| OPTION_ENV_UNSET, | |
| OPTION_ENV_CLEAR, | |
| } STANDARD_OPTIONS; | |
| static const OPTION standard_options[] = | |
| { | |
| { {"verbose", no_argument, NULL, OPTION_VERBOSE}, | |
| 'v', NULL, "Verbose output", | |
| standard_option_handler, NULL }, | |
| { {"endian", required_argument, NULL, OPTION_ENDIAN}, | |
| 'E', "B|big|L|little", "Set endianness", | |
| standard_option_handler, NULL }, | |
| /* This option isn't supported unless all choices are supported in keeping | |
| with the goal of not printing in --help output things the simulator can't | |
| do [as opposed to things that just haven't been configured in]. */ | |
| { {"environment", required_argument, NULL, OPTION_ENVIRONMENT}, | |
| '\0', "user|virtual|operating", "Set running environment", | |
| standard_option_handler }, | |
| { {"alignment", required_argument, NULL, OPTION_ALIGNMENT}, | |
| '\0', "strict|nonstrict|forced", "Set memory access alignment", | |
| standard_option_handler }, | |
| { {"debug", no_argument, NULL, OPTION_DEBUG}, | |
| 'D', NULL, "Print debugging messages", | |
| standard_option_handler }, | |
| { {"debug-insn", no_argument, NULL, OPTION_DEBUG_INSN}, | |
| '\0', NULL, "Print instruction debugging messages", | |
| standard_option_handler }, | |
| { {"debug-file", required_argument, NULL, OPTION_DEBUG_FILE}, | |
| '\0', "FILE NAME", "Specify debugging output file", | |
| standard_option_handler }, | |
| { {"do-command", required_argument, NULL, OPTION_DO_COMMAND}, | |
| '\0', "COMMAND", ""/*undocumented*/, | |
| standard_option_handler }, | |
| { {"help", no_argument, NULL, OPTION_HELP}, | |
| 'h', NULL, "Print help information", | |
| standard_option_handler }, | |
| { {"version", no_argument, NULL, OPTION_VERSION}, | |
| '\0', NULL, "Print version information", | |
| standard_option_handler }, | |
| { {"architecture", required_argument, NULL, OPTION_ARCHITECTURE}, | |
| '\0', "MACHINE", "Specify the architecture to use", | |
| standard_option_handler }, | |
| { {"architecture-info", no_argument, NULL, OPTION_ARCHITECTURE_INFO}, | |
| '\0', NULL, "List supported architectures", | |
| standard_option_handler }, | |
| { {"info-architecture", no_argument, NULL, OPTION_ARCHITECTURE_INFO}, | |
| '\0', NULL, NULL, | |
| standard_option_handler }, | |
| { {"target", required_argument, NULL, OPTION_TARGET}, | |
| '\0', "BFDNAME", "Specify the object-code format for the object files", | |
| standard_option_handler }, | |
| { {"target-info", no_argument, NULL, OPTION_TARGET_INFO}, | |
| '\0', NULL, "List supported targets", standard_option_handler }, | |
| { {"info-target", no_argument, NULL, OPTION_TARGET_INFO}, | |
| '\0', NULL, NULL, standard_option_handler }, | |
| { {"load-lma", no_argument, NULL, OPTION_LOAD_LMA}, | |
| '\0', NULL, | |
| "Use VMA or LMA addresses when loading image (default LMA)", | |
| standard_option_handler, "load-{lma,vma}" }, | |
| { {"load-vma", no_argument, NULL, OPTION_LOAD_VMA}, | |
| '\0', NULL, "", standard_option_handler, "" }, | |
| { {"sysroot", required_argument, NULL, OPTION_SYSROOT}, | |
| '\0', "SYSROOT", | |
| "Root for system calls with absolute file-names and cwd at start", | |
| standard_option_handler, NULL }, | |
| { {"argv0", required_argument, NULL, OPTION_ARGV0}, | |
| '\0', "ARGV0", "Set argv[0] to the specified string", | |
| standard_option_handler, NULL }, | |
| { {"env-set", required_argument, NULL, OPTION_ENV_SET}, | |
| '\0', "VAR=VAL", "Set the variable in the program's environment", | |
| standard_option_handler, NULL }, | |
| { {"env-unset", required_argument, NULL, OPTION_ENV_UNSET}, | |
| '\0', "VAR", "Unset the variable in the program's environment", | |
| standard_option_handler, NULL }, | |
| { {"env-clear", no_argument, NULL, OPTION_ENV_CLEAR}, | |
| '\0', NULL, "Clear the program's environment", | |
| standard_option_handler, NULL }, | |
| { {NULL, no_argument, NULL, 0}, '\0', NULL, NULL, NULL, NULL } | |
| }; | |
| static SIM_RC | |
| env_set (SIM_DESC sd, const char *arg) | |
| { | |
| int i, varlen; | |
| const char *eq; | |
| char **envp; | |
| if (STATE_PROG_ENVP (sd) == NULL) | |
| STATE_PROG_ENVP (sd) = dupargv (environ); | |
| eq = strchr (arg, '='); | |
| if (eq == NULL) | |
| { | |
| sim_io_eprintf (sd, "invalid syntax when setting env var `%s'" | |
| ": missing value", arg); | |
| return SIM_RC_FAIL; | |
| } | |
| /* Include the = in the comparison below. */ | |
| varlen = eq - arg + 1; | |
| /* If we can find an existing variable, replace it. */ | |
| envp = STATE_PROG_ENVP (sd); | |
| for (i = 0; envp[i]; ++i) | |
| { | |
| if (strncmp (envp[i], arg, varlen) == 0) | |
| { | |
| free (envp[i]); | |
| envp[i] = xstrdup (arg); | |
| break; | |
| } | |
| } | |
| /* If we didn't find the var, add it. */ | |
| if (envp[i] == NULL) | |
| { | |
| envp = xrealloc (envp, (i + 2) * sizeof (char *)); | |
| envp[i] = xstrdup (arg); | |
| envp[i + 1] = NULL; | |
| STATE_PROG_ENVP (sd) = envp; | |
| } | |
| return SIM_RC_OK; | |
| } | |
| static SIM_RC | |
| standard_option_handler (SIM_DESC sd, sim_cpu *cpu, int opt, | |
| char *arg, int is_command) | |
| { | |
| int i,n; | |
| switch ((STANDARD_OPTIONS) opt) | |
| { | |
| case OPTION_VERBOSE: | |
| STATE_VERBOSE_P (sd) = 1; | |
| break; | |
| case OPTION_ENDIAN: | |
| if (strcmp (arg, "big") == 0 || strcmp (arg, "B") == 0) | |
| { | |
| if (WITH_TARGET_BYTE_ORDER == BFD_ENDIAN_LITTLE) | |
| { | |
| sim_io_eprintf (sd, "Simulator compiled for little endian only.\n"); | |
| return SIM_RC_FAIL; | |
| } | |
| /* FIXME:wip: Need to set something in STATE_CONFIG. */ | |
| current_target_byte_order = BFD_ENDIAN_BIG; | |
| } | |
| else if (strcmp (arg, "little") == 0 || strcmp (arg, "L") == 0) | |
| { | |
| if (WITH_TARGET_BYTE_ORDER == BFD_ENDIAN_BIG) | |
| { | |
| sim_io_eprintf (sd, "Simulator compiled for big endian only.\n"); | |
| return SIM_RC_FAIL; | |
| } | |
| /* FIXME:wip: Need to set something in STATE_CONFIG. */ | |
| current_target_byte_order = BFD_ENDIAN_LITTLE; | |
| } | |
| else | |
| { | |
| sim_io_eprintf (sd, "Invalid endian specification `%s'\n", arg); | |
| return SIM_RC_FAIL; | |
| } | |
| break; | |
| case OPTION_ENVIRONMENT: | |
| if (strcmp (arg, "user") == 0) | |
| STATE_ENVIRONMENT (sd) = USER_ENVIRONMENT; | |
| else if (strcmp (arg, "virtual") == 0) | |
| STATE_ENVIRONMENT (sd) = VIRTUAL_ENVIRONMENT; | |
| else if (strcmp (arg, "operating") == 0) | |
| STATE_ENVIRONMENT (sd) = OPERATING_ENVIRONMENT; | |
| else | |
| { | |
| sim_io_eprintf (sd, "Invalid environment specification `%s'\n", arg); | |
| return SIM_RC_FAIL; | |
| } | |
| if (WITH_ENVIRONMENT != ALL_ENVIRONMENT | |
| && WITH_ENVIRONMENT != STATE_ENVIRONMENT (sd)) | |
| { | |
| const char *type; | |
| switch (WITH_ENVIRONMENT) | |
| { | |
| case USER_ENVIRONMENT: type = "user"; break; | |
| case VIRTUAL_ENVIRONMENT: type = "virtual"; break; | |
| case OPERATING_ENVIRONMENT: type = "operating"; break; | |
| default: abort (); | |
| } | |
| sim_io_eprintf (sd, "Simulator compiled for the %s environment only.\n", | |
| type); | |
| return SIM_RC_FAIL; | |
| } | |
| break; | |
| case OPTION_ALIGNMENT: | |
| if (strcmp (arg, "strict") == 0) | |
| { | |
| if (WITH_ALIGNMENT == 0 || WITH_ALIGNMENT == STRICT_ALIGNMENT) | |
| { | |
| current_alignment = STRICT_ALIGNMENT; | |
| break; | |
| } | |
| } | |
| else if (strcmp (arg, "nonstrict") == 0) | |
| { | |
| if (WITH_ALIGNMENT == 0 || WITH_ALIGNMENT == NONSTRICT_ALIGNMENT) | |
| { | |
| current_alignment = NONSTRICT_ALIGNMENT; | |
| break; | |
| } | |
| } | |
| else if (strcmp (arg, "forced") == 0) | |
| { | |
| if (WITH_ALIGNMENT == 0 || WITH_ALIGNMENT == FORCED_ALIGNMENT) | |
| { | |
| current_alignment = FORCED_ALIGNMENT; | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| sim_io_eprintf (sd, "Invalid alignment specification `%s'\n", arg); | |
| return SIM_RC_FAIL; | |
| } | |
| switch (WITH_ALIGNMENT) | |
| { | |
| case STRICT_ALIGNMENT: | |
| sim_io_eprintf (sd, "Simulator compiled for strict alignment only.\n"); | |
| break; | |
| case NONSTRICT_ALIGNMENT: | |
| sim_io_eprintf (sd, "Simulator compiled for nonstrict alignment only.\n"); | |
| break; | |
| case FORCED_ALIGNMENT: | |
| sim_io_eprintf (sd, "Simulator compiled for forced alignment only.\n"); | |
| break; | |
| default: abort (); | |
| } | |
| return SIM_RC_FAIL; | |
| case OPTION_DEBUG: | |
| if (! WITH_DEBUG) | |
| sim_io_eprintf (sd, "Debugging not compiled in, `-D' ignored\n"); | |
| else | |
| { | |
| for (n = 0; n < MAX_NR_PROCESSORS; ++n) | |
| for (i = 0; i < MAX_DEBUG_VALUES; ++i) | |
| CPU_DEBUG_FLAGS (STATE_CPU (sd, n))[i] = 1; | |
| } | |
| break; | |
| case OPTION_DEBUG_INSN : | |
| if (! WITH_DEBUG) | |
| sim_io_eprintf (sd, "Debugging not compiled in, `--debug-insn' ignored\n"); | |
| else | |
| { | |
| for (n = 0; n < MAX_NR_PROCESSORS; ++n) | |
| CPU_DEBUG_FLAGS (STATE_CPU (sd, n))[DEBUG_INSN_IDX] = 1; | |
| } | |
| break; | |
| case OPTION_DEBUG_FILE : | |
| if (! WITH_DEBUG) | |
| sim_io_eprintf (sd, "Debugging not compiled in, `--debug-file' ignored\n"); | |
| else | |
| { | |
| FILE *f = fopen (arg, "w"); | |
| if (f == NULL) | |
| { | |
| sim_io_eprintf (sd, "Unable to open debug output file `%s'\n", arg); | |
| return SIM_RC_FAIL; | |
| } | |
| for (n = 0; n < MAX_NR_PROCESSORS; ++n) | |
| CPU_DEBUG_FILE (STATE_CPU (sd, n)) = f; | |
| } | |
| break; | |
| case OPTION_DO_COMMAND: | |
| sim_do_command (sd, arg); | |
| break; | |
| case OPTION_ARCHITECTURE: | |
| { | |
| const struct bfd_arch_info *ap = bfd_scan_arch (arg); | |
| if (ap == NULL) | |
| { | |
| sim_io_eprintf (sd, "Architecture `%s' unknown\n", arg); | |
| return SIM_RC_FAIL; | |
| } | |
| STATE_ARCHITECTURE (sd) = ap; | |
| break; | |
| } | |
| case OPTION_ARCHITECTURE_INFO: | |
| { | |
| const char **list = bfd_arch_list (); | |
| const char **lp; | |
| if (list == NULL) | |
| abort (); | |
| sim_io_printf (sd, "Possible architectures:"); | |
| for (lp = list; *lp != NULL; lp++) | |
| sim_io_printf (sd, " %s", *lp); | |
| sim_io_printf (sd, "\n"); | |
| free (list); | |
| break; | |
| } | |
| case OPTION_TARGET: | |
| { | |
| STATE_TARGET (sd) = xstrdup (arg); | |
| break; | |
| } | |
| case OPTION_TARGET_INFO: | |
| { | |
| const char **list = bfd_target_list (); | |
| const char **lp; | |
| if (list == NULL) | |
| abort (); | |
| sim_io_printf (sd, "Possible targets:"); | |
| for (lp = list; *lp != NULL; lp++) | |
| sim_io_printf (sd, " %s", *lp); | |
| sim_io_printf (sd, "\n"); | |
| free (list); | |
| break; | |
| } | |
| case OPTION_LOAD_LMA: | |
| { | |
| STATE_LOAD_AT_LMA_P (sd) = 1; | |
| break; | |
| } | |
| case OPTION_LOAD_VMA: | |
| { | |
| STATE_LOAD_AT_LMA_P (sd) = 0; | |
| break; | |
| } | |
| case OPTION_HELP: | |
| sim_print_help (sd, is_command); | |
| if (STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE) | |
| exit (0); | |
| /* FIXME: 'twould be nice to do something similar if gdb. */ | |
| break; | |
| case OPTION_VERSION: | |
| sim_print_version (sd, is_command); | |
| if (STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE) | |
| exit (0); | |
| break; | |
| case OPTION_SYSROOT: | |
| /* Don't leak memory in the odd event that there's lots of | |
| --sysroot=... options. We treat "" specially since this | |
| is the statically initialized value and cannot free it. */ | |
| if (simulator_sysroot[0] != '\0') | |
| free (simulator_sysroot); | |
| if (arg[0] != '\0') | |
| simulator_sysroot = xstrdup (arg); | |
| else | |
| simulator_sysroot = ""; | |
| break; | |
| case OPTION_ARGV0: | |
| free (STATE_PROG_ARGV0 (sd)); | |
| STATE_PROG_ARGV0 (sd) = xstrdup (arg); | |
| break; | |
| case OPTION_ENV_SET: | |
| return env_set (sd, arg); | |
| case OPTION_ENV_UNSET: | |
| { | |
| int varlen; | |
| char **envp; | |
| if (STATE_PROG_ENVP (sd) == NULL) | |
| STATE_PROG_ENVP (sd) = dupargv (environ); | |
| varlen = strlen (arg); | |
| /* If we can find an existing variable, replace it. */ | |
| envp = STATE_PROG_ENVP (sd); | |
| for (i = 0; envp[i]; ++i) | |
| { | |
| char *env = envp[i]; | |
| if (strncmp (env, arg, varlen) == 0 | |
| && (env[varlen] == '\0' || env[varlen] == '=')) | |
| { | |
| free (envp[i]); | |
| break; | |
| } | |
| } | |
| /* If we clear the var, shift the array down. */ | |
| for (; envp[i]; ++i) | |
| envp[i] = envp[i + 1]; | |
| break; | |
| } | |
| case OPTION_ENV_CLEAR: | |
| freeargv (STATE_PROG_ENVP (sd)); | |
| STATE_PROG_ENVP (sd) = xmalloc (sizeof (char *)); | |
| STATE_PROG_ENVP (sd)[0] = NULL; | |
| break; | |
| } | |
| return SIM_RC_OK; | |
| } | |
| /* Add the standard option list to the simulator. */ | |
| SIM_RC | |
| standard_install (SIM_DESC sd) | |
| { | |
| SIM_ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER); | |
| if (sim_add_option_table (sd, NULL, standard_options) != SIM_RC_OK) | |
| return SIM_RC_FAIL; | |
| STATE_LOAD_AT_LMA_P (sd) = 1; | |
| return SIM_RC_OK; | |
| } | |
| /* Return non-zero if arg is a duplicate argument. | |
| If ARG is NULL, initialize. */ | |
| static int | |
| dup_arg_p (const char *arg) | |
| { | |
| static htab_t arg_table = NULL; | |
| void **slot; | |
| if (arg == NULL) | |
| { | |
| if (arg_table == NULL) | |
| arg_table = htab_create_alloc (10, htab_hash_string, | |
| htab_eq_string, NULL, | |
| xcalloc, free); | |
| htab_empty (arg_table); | |
| return 0; | |
| } | |
| slot = htab_find_slot (arg_table, arg, INSERT); | |
| if (*slot != NULL) | |
| return 1; | |
| *slot = (void *) arg; | |
| return 0; | |
| } | |
| /* Called by sim_open to parse the arguments. */ | |
| SIM_RC | |
| sim_parse_args (SIM_DESC sd, char * const *argv) | |
| { | |
| int c, i, argc, num_opts, save_opterr; | |
| char *p, *short_options; | |
| /* The `val' option struct entry is dynamically assigned for options that | |
| only come in the long form. ORIG_VAL is used to get the original value | |
| back. */ | |
| int *orig_val; | |
| struct option *lp, *long_options; | |
| const struct option_list *ol; | |
| const OPTION *opt; | |
| OPTION_HANDLER **handlers; | |
| sim_cpu **opt_cpu; | |
| SIM_RC result = SIM_RC_OK; | |
| /* Count the number of arguments. */ | |
| argc = countargv (argv); | |
| /* Count the number of options. */ | |
| num_opts = 0; | |
| for (ol = STATE_OPTIONS (sd); ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| ++num_opts; | |
| for (i = 0; i < MAX_NR_PROCESSORS; ++i) | |
| for (ol = CPU_OPTIONS (STATE_CPU (sd, i)); ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| ++num_opts; | |
| /* Initialize duplicate argument checker. */ | |
| (void) dup_arg_p (NULL); | |
| /* Build the option table for getopt. */ | |
| long_options = NZALLOC (struct option, num_opts + 1); | |
| lp = long_options; | |
| short_options = NZALLOC (char, num_opts * 3 + 1); | |
| p = short_options; | |
| handlers = NZALLOC (OPTION_HANDLER *, OPTION_START + num_opts); | |
| orig_val = NZALLOC (int, OPTION_START + num_opts); | |
| opt_cpu = NZALLOC (sim_cpu *, OPTION_START + num_opts); | |
| /* Set '+' as first char so argument permutation isn't done. This | |
| is done to stop getopt_long returning options that appear after | |
| the target program. Such options should be passed unchanged into | |
| the program image. */ | |
| *p++ = '+'; | |
| for (i = OPTION_START, ol = STATE_OPTIONS (sd); ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| { | |
| if (dup_arg_p (opt->opt.name)) | |
| continue; | |
| if (opt->shortopt != 0) | |
| { | |
| *p++ = opt->shortopt; | |
| if (opt->opt.has_arg == required_argument) | |
| *p++ = ':'; | |
| else if (opt->opt.has_arg == optional_argument) | |
| { *p++ = ':'; *p++ = ':'; } | |
| handlers[(unsigned char) opt->shortopt] = opt->handler; | |
| if (opt->opt.val != 0) | |
| orig_val[(unsigned char) opt->shortopt] = opt->opt.val; | |
| else | |
| orig_val[(unsigned char) opt->shortopt] = opt->shortopt; | |
| } | |
| if (opt->opt.name != NULL) | |
| { | |
| *lp = opt->opt; | |
| /* Dynamically assign `val' numbers for long options. */ | |
| lp->val = i++; | |
| handlers[lp->val] = opt->handler; | |
| orig_val[lp->val] = opt->opt.val; | |
| opt_cpu[lp->val] = NULL; | |
| ++lp; | |
| } | |
| } | |
| for (c = 0; c < MAX_NR_PROCESSORS; ++c) | |
| { | |
| sim_cpu *cpu = STATE_CPU (sd, c); | |
| for (ol = CPU_OPTIONS (cpu); ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| { | |
| #if 0 /* Each option is prepended with --<cpuname>- so this greatly cuts down | |
| on the need for dup_arg_p checking. Maybe in the future it'll be | |
| needed so this is just commented out, and not deleted. */ | |
| if (dup_arg_p (opt->opt.name)) | |
| continue; | |
| #endif | |
| /* Don't allow short versions of cpu specific options for now. */ | |
| if (opt->shortopt != 0) | |
| { | |
| sim_io_eprintf (sd, "internal error, short cpu specific option"); | |
| result = SIM_RC_FAIL; | |
| break; | |
| } | |
| if (opt->opt.name != NULL) | |
| { | |
| char *name; | |
| *lp = opt->opt; | |
| /* Prepend --<cpuname>- to the option. */ | |
| if (asprintf (&name, "%s-%s", CPU_NAME (cpu), lp->name) < 0) | |
| { | |
| sim_io_eprintf (sd, "internal error, out of memory"); | |
| result = SIM_RC_FAIL; | |
| break; | |
| } | |
| lp->name = name; | |
| /* Dynamically assign `val' numbers for long options. */ | |
| lp->val = i++; | |
| handlers[lp->val] = opt->handler; | |
| orig_val[lp->val] = opt->opt.val; | |
| opt_cpu[lp->val] = cpu; | |
| ++lp; | |
| } | |
| } | |
| } | |
| /* Terminate the short and long option lists. */ | |
| *p = 0; | |
| lp->name = NULL; | |
| /* Ensure getopt is initialized. */ | |
| optind = 0; | |
| /* Do not lot getopt throw errors for us. But don't mess with the state for | |
| any callers higher up by saving/restoring it. */ | |
| save_opterr = opterr; | |
| opterr = 0; | |
| while (1) | |
| { | |
| int longind, optc; | |
| optc = getopt_long (argc, argv, short_options, long_options, &longind); | |
| if (optc == -1) | |
| { | |
| if (STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE) | |
| { | |
| char **new_argv; | |
| free (STATE_PROG_FILE (sd)); | |
| STATE_PROG_FILE (sd) = NULL; | |
| /* Handle any inline variables if -- wasn't used. */ | |
| if (argv[optind] != NULL && optind > 0 | |
| && strcmp (argv[optind - 1], "--") != 0) | |
| { | |
| while (1) | |
| { | |
| const char *arg = argv[optind]; | |
| if (strchr (arg, '=') == NULL) | |
| break; | |
| env_set (sd, arg); | |
| ++optind; | |
| } | |
| } | |
| new_argv = dupargv (argv + optind); | |
| freeargv (STATE_PROG_ARGV (sd)); | |
| STATE_PROG_ARGV (sd) = new_argv; | |
| /* Skip steps when argc == 0. */ | |
| if (argv[optind] != NULL) | |
| { | |
| STATE_PROG_FILE (sd) = xstrdup (argv[optind]); | |
| if (STATE_PROG_ARGV0 (sd) != NULL) | |
| { | |
| free (new_argv[0]); | |
| new_argv[0] = xstrdup (STATE_PROG_ARGV0 (sd)); | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| if (optc == '?') | |
| { | |
| /* If getopt rejects a short option, optopt is set to the bad char. | |
| If it rejects a long option, we have to look at optind. In the | |
| short option case, argv could be multiple short options. */ | |
| const char *badopt; | |
| char optbuf[3]; | |
| if (optopt) | |
| { | |
| sprintf (optbuf, "-%c", optopt); | |
| badopt = optbuf; | |
| } | |
| else | |
| badopt = argv[optind - 1]; | |
| sim_io_eprintf (sd, | |
| "%s: unrecognized option '%s'\n" | |
| "Use --help for a complete list of options.\n", | |
| STATE_MY_NAME (sd), badopt); | |
| result = SIM_RC_FAIL; | |
| break; | |
| } | |
| if ((*handlers[optc]) (sd, opt_cpu[optc], orig_val[optc], optarg, 0/*!is_command*/) == SIM_RC_FAIL) | |
| { | |
| result = SIM_RC_FAIL; | |
| break; | |
| } | |
| } | |
| opterr = save_opterr; | |
| free (long_options); | |
| free (short_options); | |
| free (handlers); | |
| free (opt_cpu); | |
| free (orig_val); | |
| return result; | |
| } | |
| /* Utility of sim_print_help to print a list of option tables. */ | |
| static void | |
| print_help (SIM_DESC sd, sim_cpu *cpu, const struct option_list *ol, int is_command) | |
| { | |
| const OPTION *opt; | |
| for ( ; ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| { | |
| const int indent = 30; | |
| int comma, len; | |
| const OPTION *o; | |
| if (dup_arg_p (opt->opt.name)) | |
| continue; | |
| if (opt->doc == NULL) | |
| continue; | |
| if (opt->doc_name != NULL && opt->doc_name [0] == '\0') | |
| continue; | |
| sim_io_printf (sd, " "); | |
| comma = 0; | |
| len = 2; | |
| /* list any short options (aliases) for the current OPT */ | |
| if (!is_command) | |
| { | |
| o = opt; | |
| do | |
| { | |
| if (o->shortopt != '\0') | |
| { | |
| sim_io_printf (sd, "%s-%c", comma ? ", " : "", o->shortopt); | |
| len += (comma ? 2 : 0) + 2; | |
| if (o->arg != NULL) | |
| { | |
| if (o->opt.has_arg == optional_argument) | |
| { | |
| sim_io_printf (sd, "[%s]", o->arg); | |
| len += 1 + strlen (o->arg) + 1; | |
| } | |
| else | |
| { | |
| sim_io_printf (sd, " %s", o->arg); | |
| len += 1 + strlen (o->arg); | |
| } | |
| } | |
| comma = 1; | |
| } | |
| ++o; | |
| } | |
| while (OPTION_VALID_P (o) && o->doc == NULL); | |
| } | |
| /* list any long options (aliases) for the current OPT */ | |
| o = opt; | |
| do | |
| { | |
| const char *name; | |
| const char *cpu_prefix = cpu ? CPU_NAME (cpu) : NULL; | |
| if (o->doc_name != NULL) | |
| name = o->doc_name; | |
| else | |
| name = o->opt.name; | |
| if (name != NULL) | |
| { | |
| sim_io_printf (sd, "%s%s%s%s%s", | |
| comma ? ", " : "", | |
| is_command ? "" : "--", | |
| cpu ? cpu_prefix : "", | |
| cpu ? "-" : "", | |
| name); | |
| len += ((comma ? 2 : 0) | |
| + (is_command ? 0 : 2) | |
| + strlen (name)); | |
| if (o->arg != NULL) | |
| { | |
| if (o->opt.has_arg == optional_argument) | |
| { | |
| sim_io_printf (sd, "[=%s]", o->arg); | |
| len += 2 + strlen (o->arg) + 1; | |
| } | |
| else | |
| { | |
| sim_io_printf (sd, " %s", o->arg); | |
| len += 1 + strlen (o->arg); | |
| } | |
| } | |
| comma = 1; | |
| } | |
| ++o; | |
| } | |
| while (OPTION_VALID_P (o) && o->doc == NULL); | |
| if (len >= indent) | |
| { | |
| sim_io_printf (sd, "\n%*s", indent, ""); | |
| } | |
| else | |
| sim_io_printf (sd, "%*s", indent - len, ""); | |
| /* print the description, word wrap long lines */ | |
| { | |
| const char *chp = opt->doc; | |
| unsigned doc_width = 80 - indent; | |
| while (strlen (chp) >= doc_width) /* some slack */ | |
| { | |
| const char *end = chp + doc_width - 1; | |
| while (end > chp && !isspace (*end)) | |
| end --; | |
| if (end == chp) | |
| end = chp + doc_width - 1; | |
| /* The cast should be ok - its distances between to | |
| points in a string. */ | |
| sim_io_printf (sd, "%.*s\n%*s", (int) (end - chp), chp, indent, | |
| ""); | |
| chp = end; | |
| while (isspace (*chp) && *chp != '\0') | |
| chp++; | |
| } | |
| sim_io_printf (sd, "%s\n", chp); | |
| } | |
| } | |
| } | |
| /* Print help messages for the options. */ | |
| void | |
| sim_print_help (SIM_DESC sd, int is_command) | |
| { | |
| if (STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE) | |
| sim_io_printf (sd, | |
| "Usage: %s [options] [VAR=VAL|--] program [program args]\n", | |
| STATE_MY_NAME (sd)); | |
| /* Initialize duplicate argument checker. */ | |
| (void) dup_arg_p (NULL); | |
| if (STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE) | |
| sim_io_printf (sd, "Options:\n"); | |
| else | |
| sim_io_printf (sd, "Commands:\n"); | |
| print_help (sd, NULL, STATE_OPTIONS (sd), is_command); | |
| sim_io_printf (sd, "\n"); | |
| /* Print cpu-specific options. */ | |
| { | |
| int i; | |
| for (i = 0; i < MAX_NR_PROCESSORS; ++i) | |
| { | |
| sim_cpu *cpu = STATE_CPU (sd, i); | |
| if (CPU_OPTIONS (cpu) == NULL) | |
| continue; | |
| sim_io_printf (sd, "CPU %s specific options:\n", CPU_NAME (cpu)); | |
| print_help (sd, cpu, CPU_OPTIONS (cpu), is_command); | |
| sim_io_printf (sd, "\n"); | |
| } | |
| } | |
| sim_io_printf (sd, "Note: Depending on the simulator configuration some %ss\n", | |
| STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE ? "option" : "command"); | |
| sim_io_printf (sd, " may not be applicable\n"); | |
| if (STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE) | |
| { | |
| sim_io_printf (sd, "\n"); | |
| sim_io_printf (sd, | |
| "VAR=VAL Environment variables to set. " | |
| "Ignored if -- is used.\n"); | |
| sim_io_printf (sd, "program args Arguments to pass to simulated program.\n"); | |
| sim_io_printf (sd, " Note: Very few simulators support this.\n"); | |
| } | |
| } | |
| /* Print version information. */ | |
| void | |
| sim_print_version (SIM_DESC sd, int is_command) | |
| { | |
| sim_io_printf (sd, "GNU simulator %s%s\n", PKGVERSION, version); | |
| sim_io_printf (sd, "Copyright (C) 2024 Free Software Foundation, Inc.\n"); | |
| /* Following the copyright is a brief statement that the program is | |
| free software, that users are free to copy and change it on | |
| certain conditions, that it is covered by the GNU GPL, and that | |
| there is no warranty. */ | |
| sim_io_printf (sd, "\ | |
| License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>\ | |
| \nThis is free software: you are free to change and redistribute it.\n\ | |
| There is NO WARRANTY, to the extent permitted by law.\n"); | |
| if (!is_command) | |
| return; | |
| sim_io_printf (sd, "This SIM was configured as:\n"); | |
| sim_config_print (sd); | |
| if (REPORT_BUGS_TO[0]) | |
| { | |
| sim_io_printf (sd, "For bug reporting instructions, please see:\n\ | |
| %s.\n", | |
| REPORT_BUGS_TO); | |
| } | |
| sim_io_printf (sd, "Find the SIM homepage & other documentation resources \ | |
| online at:\n <https://sourceware.org/gdb/wiki/Sim/>.\n"); | |
| } | |
| /* Utility of sim_args_command to find the closest match for a command. | |
| Commands that have "-" in them can be specified as separate words. | |
| e.g. sim memory-region 0x800000,0x4000 | |
| or sim memory region 0x800000,0x4000 | |
| If CPU is non-null, use its option table list, otherwise use the main one. | |
| *PARGI is where to start looking in ARGV. It is updated to point past | |
| the found option. */ | |
| static const OPTION * | |
| find_match (SIM_DESC sd, sim_cpu *cpu, char *argv[], int *pargi) | |
| { | |
| const struct option_list *ol; | |
| const OPTION *opt; | |
| /* most recent option match */ | |
| const OPTION *matching_opt = NULL; | |
| int matching_argi = -1; | |
| if (cpu) | |
| ol = CPU_OPTIONS (cpu); | |
| else | |
| ol = STATE_OPTIONS (sd); | |
| /* Skip passed elements specified by *PARGI. */ | |
| argv += *pargi; | |
| for ( ; ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| { | |
| int argi = 0; | |
| const char *name = opt->opt.name; | |
| if (name == NULL) | |
| continue; | |
| while (argv [argi] != NULL | |
| && strncmp (name, argv [argi], strlen (argv [argi])) == 0) | |
| { | |
| name = &name [strlen (argv[argi])]; | |
| if (name [0] == '-') | |
| { | |
| /* leading match ...<a-b-c>-d-e-f - continue search */ | |
| name ++; /* skip `-' */ | |
| argi ++; | |
| continue; | |
| } | |
| else if (name [0] == '\0') | |
| { | |
| /* exact match ...<a-b-c-d-e-f> - better than before? */ | |
| if (argi > matching_argi) | |
| { | |
| matching_argi = argi; | |
| matching_opt = opt; | |
| } | |
| break; | |
| } | |
| else | |
| break; | |
| } | |
| } | |
| *pargi = matching_argi; | |
| return matching_opt; | |
| } | |
| static char ** | |
| complete_option_list (char **ret, size_t *cnt, const struct option_list *ol, | |
| const char *text, const char *word) | |
| { | |
| const OPTION *opt = NULL; | |
| size_t len = strlen (word); | |
| for ( ; ol != NULL; ol = ol->next) | |
| for (opt = ol->options; OPTION_VALID_P (opt); ++opt) | |
| { | |
| const char *name = opt->opt.name; | |
| /* A long option to match against? */ | |
| if (!name) | |
| continue; | |
| /* Does this option actually match? */ | |
| if (strncmp (name, word, len)) | |
| continue; | |
| ret = xrealloc (ret, ++*cnt * sizeof (ret[0])); | |
| ret[*cnt - 2] = xstrdup (name); | |
| } | |
| return ret; | |
| } | |
| /* All leading text is stored in @text, while the current word being | |
| completed is stored in @word. Trailing text of @word is not. */ | |
| char ** | |
| sim_complete_command (SIM_DESC sd, const char *text, const char *word) | |
| { | |
| char **ret = NULL; | |
| size_t cnt = 1; | |
| sim_cpu *cpu; | |
| /* Only complete first word for now. */ | |
| if (text != word) | |
| return ret; | |
| cpu = STATE_CPU (sd, 0); | |
| if (cpu) | |
| ret = complete_option_list (ret, &cnt, CPU_OPTIONS (cpu), text, word); | |
| ret = complete_option_list (ret, &cnt, STATE_OPTIONS (sd), text, word); | |
| if (ret) | |
| ret[cnt - 1] = NULL; | |
| return ret; | |
| } | |
| SIM_RC | |
| sim_args_command (SIM_DESC sd, const char *cmd) | |
| { | |
| /* something to do? */ | |
| if (cmd == NULL) | |
| return SIM_RC_OK; /* FIXME - perhaps help would be better */ | |
| if (cmd [0] == '-') | |
| { | |
| /* user specified -<opt> ... form? */ | |
| char **argv = buildargv (cmd); | |
| SIM_RC rc = sim_parse_args (sd, argv); | |
| freeargv (argv); | |
| return rc; | |
| } | |
| else | |
| { | |
| char **argv = buildargv (cmd); | |
| const OPTION *matching_opt = NULL; | |
| int matching_argi; | |
| sim_cpu *cpu; | |
| if (argv [0] == NULL) | |
| { | |
| freeargv (argv); | |
| return SIM_RC_OK; /* FIXME - perhaps help would be better */ | |
| } | |
| /* First check for a cpu selector. */ | |
| { | |
| char *cpu_name = xstrdup (argv[0]); | |
| char *hyphen = strchr (cpu_name, '-'); | |
| if (hyphen) | |
| *hyphen = 0; | |
| cpu = sim_cpu_lookup (sd, cpu_name); | |
| if (cpu) | |
| { | |
| /* If <cpuname>-<command>, point argv[0] at <command>. */ | |
| if (hyphen) | |
| { | |
| matching_argi = 0; | |
| argv[0] += hyphen - cpu_name + 1; | |
| } | |
| else | |
| matching_argi = 1; | |
| matching_opt = find_match (sd, cpu, argv, &matching_argi); | |
| /* If hyphen found restore argv[0]. */ | |
| if (hyphen) | |
| argv[0] -= hyphen - cpu_name + 1; | |
| } | |
| free (cpu_name); | |
| } | |
| /* If that failed, try the main table. */ | |
| if (matching_opt == NULL) | |
| { | |
| matching_argi = 0; | |
| matching_opt = find_match (sd, NULL, argv, &matching_argi); | |
| } | |
| if (matching_opt != NULL) | |
| { | |
| switch (matching_opt->opt.has_arg) | |
| { | |
| case no_argument: | |
| if (argv [matching_argi + 1] == NULL) | |
| matching_opt->handler (sd, cpu, matching_opt->opt.val, | |
| NULL, 1/*is_command*/); | |
| else | |
| sim_io_eprintf (sd, "Command `%s' takes no arguments\n", | |
| matching_opt->opt.name); | |
| break; | |
| case optional_argument: | |
| if (argv [matching_argi + 1] == NULL) | |
| matching_opt->handler (sd, cpu, matching_opt->opt.val, | |
| NULL, 1/*is_command*/); | |
| else if (argv [matching_argi + 2] == NULL) | |
| matching_opt->handler (sd, cpu, matching_opt->opt.val, | |
| argv [matching_argi + 1], 1/*is_command*/); | |
| else | |
| sim_io_eprintf (sd, "Command `%s' requires no more than one argument\n", | |
| matching_opt->opt.name); | |
| break; | |
| case required_argument: | |
| if (argv [matching_argi + 1] == NULL) | |
| sim_io_eprintf (sd, "Command `%s' requires an argument\n", | |
| matching_opt->opt.name); | |
| else if (argv [matching_argi + 2] == NULL) | |
| matching_opt->handler (sd, cpu, matching_opt->opt.val, | |
| argv [matching_argi + 1], 1/*is_command*/); | |
| else | |
| sim_io_eprintf (sd, "Command `%s' requires only one argument\n", | |
| matching_opt->opt.name); | |
| } | |
| freeargv (argv); | |
| return SIM_RC_OK; | |
| } | |
| freeargv (argv); | |
| } | |
| /* didn't find anything that remotly matched */ | |
| return SIM_RC_FAIL; | |
| } |