Implements a .breakpoint command

This commit is contained in:
Yann Weber 2023-04-03 21:09:27 +02:00
commit 53d2b40af9
6 changed files with 303 additions and 5 deletions

View file

@ -1,8 +1,9 @@
bin_PROGRAMS = asmsh child
libcheck_asmsh_a_SOURCES = mmap_parse.c asm_env.c breakpoints.c compile.c logger.c \
completion.c shell.c shell_cmds.c shell_sym.c \
history.c
completion.c history.c \
shell.c shell_cmds.c shell_sym.c \
shell_cmd_breakpoint.c
asmsh_SOURCES = asmsh.c $(libcheck_asmsh_a_SOURCES)
child_SOURCES = child.s

View file

@ -39,6 +39,8 @@ asmsh_env_t* asmsh_env(const char *childpath)
}
child_mmap_init(&(res->mmap));
asmsh_brk_init(&res->brks);
res->childpath = NULL;
if(childpath && (res->childpath = strdup(childpath)) == NULL)
{
@ -86,6 +88,7 @@ void asmsh_env_free(asmsh_env_t *asmenv)
free(asmenv->mmap.maps);
}
kill(asmenv->pid, SIGKILL);
asmsh_brk_free(&asmenv->brks);
free(asmenv->childpath);
free(asmenv);
}

View file

@ -27,6 +27,7 @@
#include "mmap_parse.h"
#include "compile.h"
#include "breakpoints.h"
///! Initial size of the child's memory map with PROT_EXEC permission
#define ASMSH_CHILD_TEXT_MAP_SZ 0x1000 // defined in child.s
@ -56,6 +57,10 @@ struct asmsh_env_s
/** Pointer on current write addr in child's map */
unsigned char *txt_map_ptr;
/** List of breakpoints addresses in child's memory */
asmsh_brk_t brks;
/** @todo check & delete useless properties */
/** Pointer on the next addr where we should write some
* compiled bytecode */
unsigned char *code_write_ptr;

10
asmsh.h
View file

@ -8,7 +8,7 @@ A shell designed to run assembly (for the moment only x86_64 is supported).
A simple programm is spawned by the shell, and each instructions are runned in the
subprocess environment.
@section UI
@section man_ui USER INTERFACE
For the moment, the UI is implemented using GNU readline with basic support for
completion (using tab).
@ -16,6 +16,9 @@ completion (using tab).
The prompt is composed like "asmsh@RIPVAL > " where RIPVAL is the RIP register (
Instruction Pointer ) value in hexadecimal.
Other readline(3) features are available like up arrow to display previous commands,
C^R for history search, etc.
@section INSTRUCTIONS
The shell uses the GNU as compiler from binutils, the instructions syntax
@ -109,12 +112,13 @@ asmsh@0x7f6e312e5022 >
@section TODO TODOLIST
@todo Implement breakpoints
@todo Implement command for memory read/dump
@todo Implement symbols for jumps
@todo Add support for label declarations & references
@todo Implement write without exec
@todo Implement function declaration
@todo Implement command for memory read/dump
@todo Add switch between intel's & AT&T's syntaxes.
@todo Add support for label declarations & references
@todo Rationalise commands argument parsing
@section AUTHOR
Written by Yann Weber <yann.weber@members.fsf.org>

279
shell_cmd_breakpoint.c Normal file
View file

@ -0,0 +1,279 @@
/* Copyright Yann Weber <asmsh@yannweb.net>
This file is part of asmsh.
asmsh 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 any later version.
asmsh 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 asmsh. If not, see <https://www.gnu.org/licenses/>.
*/
#include "shell_cmds.h"
/** Add a breakpoint
* @param asmsh_t* The shell
* @param asmsh_cmd_args_t* The command arguments
* @param int The first argument of the expression
* @return -1 on error else 0
*/
static int brk_add(asmsh_t *sh, asmsh_cmd_args_t *args, int expr_first);
/** Remove a breakpoint
* @param asmsh_t* The shell
* @param asmsh_cmd_args_t* The command arguments
* @param int The first argument of the expression
* @return -1 on error else 0
*/
static int brk_del(asmsh_t *sh, asmsh_cmd_args_t *args, int expr_first);
/** List all breakpoints
* @param asmsh_t* The shell
* @param asmsh_cmd_args_t* The command arguments
* @param int The first argument of the expression
* @return -1 on error else 0
* @todo add filters arguments
*/
static int brk_ls(asmsh_t *sh, asmsh_cmd_args_t *args, int expr_first);
/** Parse a breakpoint address expression
*
* An expression must be of the form A [OP B] with A and B
* an integer or '.' for current RIP value and OP one of + or -
*
* @param asmsh_cmd_args_t* The breakpoint command argument list
* @param int The first argument of the address expression
* @param const unsigned long RIP value
* @param unsigned long* A pointer on the result address
*
* @returns -1 on error and 0 if ok
*/
static int parse_addr_expr(asmsh_cmd_args_t *args, int first,
const unsigned long rip, unsigned long *addr);
/** Utility function for @ref parse_addr_expr() that parse an expression value.
*
* An expression can be . for rip value or an unsigned long, possibly in hex, etc
* @param const char* The value
* @param unsigned long The value of RIP
* @param unsigned long* A pointer on the result
* @return -1 on error else 0
*/
static int addr_expr_val(const char *val, unsigned long rip, unsigned long *res);
void log_brk_usage()
{
asmsh_log_info("Available commands are : add, a, del, d, rm, list, ls, l");
}
void log_expr_usage()
{
asmsh_log_info("Bad argument, expected an expression of the form A + B or A - B with A and B an integer or '.' (for current RIP value)");
}
int asmsh_cmd_breakpoint(asmsh_t *sh, asmsh_cmd_args_t *args)
{
int ret;
if(args->argc == 0 || args->args[0][0] == '.' || \
(args->args[0][0] >= '0' && args->args[0][0] <= '9'))
{
if((ret = brk_add(sh, args, 0)) < 0)
{
log_expr_usage();
}
return ret;
}
// At least 1 arg, 1st arg chr is not in [0-9] nor is '.'
// Looking for specific commands argument
if(strcmp(args->args[0], "a") == 0 || \
strcmp(args->args[0], "add") == 0)
{
if((ret = brk_add(sh, args, 1)) < 0)
{
log_expr_usage();
}
return ret;
}
else if(strcmp(args->args[0], "d") == 0 || \
strcmp(args->args[0], "del") == 0 || \
strcmp(args->args[0], "rm") == 0)
{
ret = brk_del(sh, args, 1);
return ret;
}
else if(strcmp(args->args[0], "list") == 0 || \
strcmp(args->args[0], "ls") == 0 || \
strcmp(args->args[0], "l") == 0)
{
ret = brk_ls(sh, args, 1);
return ret;
}
else
{
asmsh_log_error("Unrecognized action '%s'", args->args[0]);
log_brk_usage();
}
return -1;
}
static int brk_add(asmsh_t *sh, asmsh_cmd_args_t *args, int expr_first)
{
const unsigned long rip = sh->env->regs.rip;
unsigned long addr;
if(parse_addr_expr(args, expr_first, rip, &addr) < 0)
{
return -1;
}
int ret = asmsh_brk_add(&sh->env->brks, addr);
if(ret < 0)
{
int err = errno;
asmsh_log_error("Unable to set breakpoint @ %016lX : %s",
addr, strerror(errno));
errno = err;
return -1;
}
else if(ret > 0)
{
asmsh_log_warning("Breakpoint @ %016lX allready set", addr);
return -1;
}
asmsh_log_info("Set breakpoint @ %016lX", addr);
return 0;
}
static int brk_del(asmsh_t *sh, asmsh_cmd_args_t *args, int expr_first)
{
const unsigned long rip = sh->env->regs.rip;
unsigned long addr;
if(parse_addr_expr(args, expr_first, rip, &addr) < 0)
{
return -1;
}
int ret = asmsh_brk_del(&sh->env->brks, addr);
if(ret < 0)
{
int err = errno;
asmsh_log_error("Unable to remove breakpoint @ %016lX : %s",
addr, strerror(errno));
errno = err;
return -1;
}
else if(ret > 0)
{
asmsh_log_warning("Breakpoint @ %016lX do not exists", addr);
return -1;
}
asmsh_log_info("Removed breakpoint @ %016lX", addr);
return 0;
}
static int brk_ls(asmsh_t *sh, asmsh_cmd_args_t *args, int expr_first)
{
for(size_t i=0; i<sh->env->brks.sz; i++)
{
printf("0x%016lx\n", sh->env->brks.addrs[i]);
}
printf("%ld breakpoints\n", sh->env->brks.sz);
return 0;
}
static int addr_expr_val(const char *val, unsigned long rip, unsigned long *res)
{
if(strcmp(".", val) == 0)
{
*res = rip;
return 0;
}
char *endptr;
errno = 0;
*res = strtoul(val, &endptr, 0);
if(errno != 0)
{
return -1;
}
else if(*endptr != '\0')
{
errno = EINVAL;
return -1;
}
return 0;
}
static int parse_addr_expr(asmsh_cmd_args_t *args, int first,
const unsigned long rip, unsigned long *addr)
{
char * const * argv = args->args + first;
const int argc = args->argc - first;
unsigned long val[2];
switch(argc)
{
case 0:
*addr = rip;
break;
case 1:
if(addr_expr_val(argv[0], rip, addr) < 0)
{
int err = errno;
asmsh_log_error("Invalid expression value '%s': %s",
argv[0], strerror(errno));
errno = err;
return -1;
}
break;
case 3:
for(int i=0; i<2; i++)
{
const char *arg = i ? argv[2]:argv[0];
if(addr_expr_val(arg, rip, &val[i]) < 0)
{
asmsh_log_error("Invalid value '%s' in expression '%s %s %s' : %s",
arg,
argv[0], argv[1], argv[2],
strerror(errno));
return -1;
}
}
const char operator = strlen(argv[1]) == 1 ? argv[1][0]: '\0';
switch(operator)
{
case '+':
*addr = val[0] + val[1];
break;
case '-':
*addr = val[0] - val[1];
break;
default:
asmsh_log_error("Invalid operator '%s' in expression '%s %s %s'",
argv[1],
argv[0], argv[1], argv[2]);
return -1;
}
break;
default:
//USAGE !
asmsh_log_error("Unexexpected argument count for an expression. Expecting one of 0, 1 or 3 but got %d", argc);
return -1;
}
return 0;
}

View file

@ -91,6 +91,9 @@ const char *asmsh_cmd_help(asmsh_t *sh);
// Quit the shell
int asmsh_cmd_quit(asmsh_t *sh, asmsh_cmd_args_t *args);
// Defined in @file shell_cmd_breakpoint.c
int asmsh_cmd_breakpoint(asmsh_t *sh, asmsh_cmd_args_t *args);
// Print an instruction bytecode
int asmsh_cmd_bcode_(asmsh_t *sh, char *buf, int bufsz, int argc, char **args);
int asmsh_cmd_bcode(asmsh_t *sh, asmsh_cmd_args_t *args);
@ -116,6 +119,9 @@ int asmsh_cmd_help_(asmsh_t *sh, asmsh_cmd_args_t *args);
* The list of shell commands
*/
static const asmsh_cmd_t asmsh_CMDS[] = {
{".breakpoint", asmsh_cmd_breakpoint, 3,
".br(eakpoint)", "[addr]",
"Set a breakpoint"},
{".bytecode", asmsh_cmd_bcode, 2,
".b(ytecode)", "",
"display last instruction bytecode"},