diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 7c39715d..0728bedf 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -616,6 +616,22 @@ str8_is_integer(String8 string, U32 radix) return result; } +internal B32 +str8_is_integer_signed(String8 string, U32 radix) +{ + B32 result = 0; + String8 sign = str8_prefix(string, 1); + if(str8_match(sign, str8_lit("-"), 0)) + { + result = str8_is_integer(str8_skip(string, 1), radix); + } + else + { + result = str8_is_integer(string, radix); + } + return result; +} + internal U64 u64_from_str8(String8 string, U32 radix) { diff --git a/src/base/base_strings.h b/src/base/base_strings.h index 5a14e3dc..37c7acb2 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -233,6 +233,7 @@ internal String8 push_cstr(Arena *arena, String8 str); // TODO(rjf): this is unn //- rjf: string -> integer internal S64 sign_from_str8(String8 string, String8 *string_tail); internal B32 str8_is_integer(String8 string, U32 radix); +internal B32 str8_is_integer_signed(String8 string, U32 radix); internal U64 u64_from_str8(String8 string, U32 radix); internal S64 s64_from_str8(String8 string, U32 radix); internal U32 u32_from_str8(String8 string, U32 radix); diff --git a/src/stap/stap_parse.c b/src/stap/stap_parse.c new file mode 100644 index 00000000..932b47d0 --- /dev/null +++ b/src/stap/stap_parse.c @@ -0,0 +1,364 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal B32 +stap_is_scale_valid(U64 scale) +{ + local_persist B8 valid_sizes[9] = { 0, 1, 1, 0, 1, 0, 0, 0, 1 }; + B32 is_valid = scale < ArrayCount(valid_sizes) && valid_sizes[scale]; + return is_valid; +} + +internal U64 +stap_parse_digit(U8 *begin, U8 *end, U64 *digit_out) +{ + U8 *ptr = begin; + if (ptr < end && *ptr == '-') { + ptr += 1; + } + for (;ptr < end; ptr += 1) { + if (!char_is_digit(*ptr, 10)) { break; } + } + String8 digit_str = str8(begin, (U64)(ptr - begin)); + if (digit_out) { + *digit_out = u64_from_str8(digit_str, 10); + } + return digit_str.size; +} + +internal U64 +stap_skip_whitespace(U8 *begin, U8 *end) +{ + U8 *ptr = begin; + for (; ptr < end && *ptr == ' '; ptr += 1); + U64 size = (U64)(ptr - begin); + return size; +} + +internal U64 +stap_parse_ident(U8 *begin, U8 *end, String8 *ident_out) +{ + U8 *ptr = begin; + for (; ptr < end; ptr += 1) { + if (!char_is_alpha(*ptr) && !char_is_digit(*ptr, 10)) { break; } + } + String8 ident = str8(begin, (U64)(ptr - begin)); + if (ident_out) { *ident_out = ident; } + return ident.size; +} + +internal U64 +stap_size_from_arg(String8 string) +{ + U64 tag_sep = str8_find_needle(string, 0, str8_lit("@"), 0); + U64 next_tag_sep = str8_find_needle(string, tag_sep + 1, str8_lit("@"), 0); + U64 arg_end = str8_find_needle_reverse(string, next_tag_sep, str8_lit(" "), 0); + return arg_end == 0 ? string.size : arg_end; +} + +internal String8List +stap_list_from_string(Arena *arena, String8 string) +{ + String8List result = {0}; + for (U64 cursor = 0, arg_size; cursor < string.size; cursor += arg_size) { + arg_size = stap_size_from_arg(str8_skip(string, cursor)); + String8 arg = str8_substr(string, r1u64(cursor, cursor + arg_size)); + str8_list_push(arena, &result, arg); + } + return result; +} + +internal String8 +stap_parse_args_x64(String8 string, STAP_Arg *arg_out) +{ + String8 error = str8_lit("unknown parse error"); + U64 sep_pos = str8_find_needle(string, 0, str8_lit("@"), 0); + if (sep_pos < string.size) { + STAP_Arg arg = {0}; + + String8 tag = str8_prefix(string, sep_pos + 1); + String8 oper = str8_skip(string, tag.size); + + STAP_ArgValueType arg_value_type = STAP_ArgValueType_Null; + U64 arg_value_size = 0; + B32 infer_arg_value_size = 1; + U64 tag_size = 0; + { + U8 *ptr = tag.str, *end = tag.str + tag.size; + + // is signed? + if (ptr >= end) { goto operand_parse_exit; } + if (*ptr == '-') { + arg_value_type = STAP_ArgValueType_S; + ptr += 1; + } + + // parse optional size + if (ptr >= end) { goto operand_parse_exit; } + if (char_is_digit(*ptr, 10)) { + arg_value_size = *ptr - '0'; + if (!stap_is_scale_valid(arg_value_size)) { + error = str8_lit("invalid operand size"); + goto operand_parse_exit; + } + ptr += 1; + infer_arg_value_size = 0; + } + + // is this float? + if (ptr >= end) { goto operand_parse_exit; } + if (*ptr == 'f') { + if (arg_value_type != STAP_ArgValueType_Null) { + error = str8_lit("illegal combination of modifiers '-' and 'f'"); + goto operand_parse_exit; + } + ptr += 1; + arg_value_type = STAP_ArgValueType_F; + } + + // assume default value type to be unsigned + if (arg_value_type == STAP_ArgValueType_Null) { + arg_value_type = STAP_ArgValueType_U; + } + + if (ptr >= end) { goto operand_parse_exit; } + if (*ptr != '@') { + error = str8_lit("failed to find @"); + goto operand_parse_exit; + } + ptr += 1; + + tag_size = (U64)(ptr - tag.str); + } + + U64 memory_ref_start = str8_find_needle_reverse(oper, 0, str8_lit("("), 0); + // memory operand + if (memory_ref_start > 0 && memory_ref_start < oper.size) { + // + // Expected syntax for memory operand: { [Disp] (Base [',' Index [',' Scale]]) } | { $IMM } | { %reg } + // + // Disp: { [+|-]Digits } | { Expr } + // Base: %reg + // Index: %reg + // Scale: 1|2|4|8 + // Expr: A small C-like infix expression + // + + // find closing ')' + U64 memory_ref_end = str8_find_needle(oper, memory_ref_start+1, str8_lit(")"), 0); + if (memory_ref_end > oper.size) { + error = str8_lit("missing closing ')'"); + goto operand_parse_exit; + } + if (memory_ref_end >= oper.size) { + error = str8_lit("expression does not terminate after ')'"); + goto operand_parse_exit; + } + + // parse displacement + String8 disp_str = str8_skip_chop_whitespace(str8_prefix(oper, memory_ref_start-1)); + if (disp_str.size > 0 && !str8_is_integer_signed(disp_str, 10)) { + error = str8_lit("displacement is too complicated, only integral displacement is supported"); + goto operand_parse_exit; + } + S64 disp = s64_from_str8(disp_str, 10); + + // parse base, index, scale + String8 base_index_scale = str8_skip_chop_whitespace(str8_substr(oper, r1u64(memory_ref_start, memory_ref_end))); + U64 first_comma = str8_find_needle(base_index_scale, 0, str8_lit(","), 0); + U64 second_comma = str8_find_needle(base_index_scale, first_comma+1, str8_lit(","), 0); + String8 base_str = str8_skip_chop_whitespace(str8_substr(base_index_scale, r1u64(0, first_comma))); + String8 index_str = str8_skip_chop_whitespace(str8_substr(base_index_scale, r1u64(first_comma + 1, second_comma))); + String8 scale_str = str8_skip_chop_whitespace(str8_substr(base_index_scale, r1u64(second_comma + 1, memory_ref_end))); + + // syntax check + if(first_comma >= base_index_scale.size && base_str.size == 0) { + error = str8_lit("missing base register"); + goto operand_parse_exit; + } + if (first_comma < base_index_scale.size && index_str.size == 0) { + error = str8_lit("missing index register"); + goto operand_parse_exit; + } + if (second_comma < base_index_scale.size && scale_str.size == 0) { + error = str8_lit("missing scale"); + goto operand_parse_exit; + } + if (base_str.size > 0 && !str8_match(str8_lit("%"), base_str, StringMatchFlag_RightSideSloppy)) { + error = str8_lit("invalid base register"); + goto operand_parse_exit; + } + if (first_comma < base_index_scale.size && (index_str.size < 2 || !str8_match(str8_lit("%"), index_str, StringMatchFlag_RightSideSloppy))) { + error = str8_lit("invalid index register"); + goto operand_parse_exit; + } + if (second_comma < base_index_scale.size && (scale_str.size > 0 && !str8_is_integer(scale_str, 10))) { + error = str8_lit("invalid of scale (expected unsigned integral type)"); + goto operand_parse_exit; + } + + // stip '%' prefix + base_str = str8_skip(base_str, 1); + index_str = str8_skip(index_str, 1); + + // parse base + B8 base_is_alias = 0; + U32 base_reg_code = 0; + if (base_str.size) { + base_reg_code = regs_reg_code_from_name(Arch_x64, base_str); + if (base_reg_code == 0) { + base_reg_code = regs_alias_code_from_name(Arch_x64, base_str); + base_is_alias = base_reg_code != 0; + } + if (base_reg_code == 0) { + error = str8_lit("unknown base register"); + goto operand_parse_exit; + } + } + + // parse index + B8 index_is_alias = 0; + U32 index_reg_code = 0; + if (index_str.size) { + index_reg_code = regs_reg_code_from_name(Arch_x64, index_str); + if (index_reg_code == 0) { + index_reg_code = regs_alias_code_from_name(Arch_x64, index_str); + index_is_alias = index_reg_code != 0; + } + if (index_reg_code == 0) { + error = str8_lit("unknown index register"); + } + } + + // parse scale + U64 scale = 1; + if (scale_str.size) { + scale = u64_from_str8(scale_str, 10); + if (!stap_is_scale_valid(scale)) { + error = str8_lit("invalid scale value (expected 1/2/4/8)"); + goto operand_parse_exit; + } + } + + if (infer_arg_value_size) { + error = str8_lit("memory operands must have a sice"); + goto operand_parse_exit; + } + + // fill out memory ref portion + arg.type = STAP_ArgType_MemoryRef; + arg.memory_ref.disp = disp; + arg.memory_ref.base.reg_code = base_reg_code; + arg.memory_ref.base.is_alias = base_is_alias; + arg.memory_ref.index.reg_code = index_reg_code; + arg.memory_ref.index.is_alias = index_is_alias; + arg.memory_ref.scale = scale; + } + // $imm + else if (str8_match(str8_lit("$"), oper, StringMatchFlag_RightSideSloppy)) { + String8 imm_str = str8_skip(oper, 1); + + U64 imm_size = str8_find_needle(imm_str, 0, str8_lit(" "), 0); + if (imm_size > imm_str.size ) { + imm_size = imm_str.size; + } + imm_str = str8_prefix(imm_str, imm_size); + + if (imm_str.size == 0) { + goto operand_parse_exit; + } + + U64 imm = 0; + B32 is_parsed = 0; + switch (arg_value_type) { + case STAP_ArgValueType_Null: + case STAP_ArgValueType_U: + case STAP_ArgValueType_F: { + is_parsed = try_u64_from_str8_c_rules(imm_str, &imm); + } break; + case STAP_ArgValueType_S: { + is_parsed = try_s64_from_str8_c_rules(imm_str, (S64 *)&imm); + } break; + default: { InvalidPath; } break; + } + + if (!is_parsed) { + error = str8_lit("failed to parse immediate"); + goto operand_parse_exit; + } + + arg.type = STAP_ArgType_Imm; + arg.imm = imm; + } + // %reg + else if (str8_match(str8_lit("%"), oper, StringMatchFlag_RightSideSloppy)) { + // skip % + String8 reg_str = str8_skip(oper, 1); + if (reg_str.size == 0) { + goto operand_parse_exit; + } + + B8 is_reg_alias = 0; + U32 reg_code = regs_reg_code_from_name(Arch_x64, reg_str); + if (reg_code == 0) { + reg_code = regs_alias_code_from_name(Arch_x64, reg_str); + is_reg_alias = reg_code != 0; + } + if (reg_code == 0) { + error = str8_lit("invalid register name"); + goto operand_parse_exit; + } + + arg.type = STAP_ArgType_Reg; + arg.reg.is_alias = is_reg_alias; + arg.reg.reg_code = reg_code; + } else { + goto operand_parse_exit; + } + + // fill out value portion + arg.value_type = arg_value_type == STAP_ArgValueType_Null ? STAP_ArgValueType_U : arg_value_type; + arg.value_size = arg_value_size; + + // write output + if (arg_out) { + *arg_out = arg; + } + + // clear error tracker + error = str8_zero(); + +operand_parse_exit:; + } else { + error = str8_lit("invalid argument string"); + } + + return error; +} + +internal STAP_ArgArray +stap_arg_array_from_string(Arena *arena, Arch arch, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List arg_strings = stap_list_from_string(scratch.arena, string); + STAP_ArgArray result = {0}; + result.v = push_array(arena, STAP_Arg, arg_strings.node_count); + for EachNode(n, String8Node, arg_strings.first) { + STAP_Arg *arg = &result.v[result.count++]; + + switch (arch) { + case Arch_Null: {} break; + case Arch_x64: { + stap_parse_args_x64(n->string, arg); + } break; + case Arch_x86: + case Arch_arm32: + case Arch_arm64: { NotImplemented; } break; + default: { InvalidPath; } break; + } + } + scratch_end(scratch); + return result; +} + + diff --git a/src/stap/stap_parse.h b/src/stap/stap_parse.h new file mode 100644 index 00000000..74897711 --- /dev/null +++ b/src/stap/stap_parse.h @@ -0,0 +1,74 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef STAP_PARSE_H +#define STAP_PARSE_H + +typedef enum STAP_ArgType +{ + STAP_ArgType_Null, + STAP_ArgType_Imm, + STAP_ArgType_Reg, + STAP_ArgType_MemoryRef, +} STAP_ArgType; + +typedef enum STAP_ArgValueType +{ + STAP_ArgValueType_Null, + STAP_ArgValueType_U, + STAP_ArgValueType_S, + STAP_ArgValueType_F, +} STAP_ArgValueType; + +typedef struct STAP_Arg +{ + STAP_ArgValueType value_type; + U64 value_size; + STAP_ArgType type; + union { + U64 imm; + struct { + U32 reg_code; + B8 is_alias; + } reg; + struct { + S64 disp; + struct { + U32 reg_code; + B8 is_alias; + } base; + struct { + U32 reg_code; + B8 is_alias; + } index; + U64 scale; + } memory_ref; + }; +} STAP_Arg; + +typedef struct STAP_ArgArray +{ + U64 count; + STAP_Arg *v; +} STAP_ArgArray; + +typedef struct STAP_ArgValue +{ + STAP_ArgValueType value_type; + U8 value_size; + void *raw_ptr; +} STAP_ArgValue; + +typedef struct STAP_ArgValueArray +{ + U64 count; + STAP_ArgValue *v; +} STAP_ArgValueArray; + +//////////////////////////////// + +internal String8 stap_parse_args_x64(String8 string, STAP_Arg *arg_out); +internal STAP_ArgArray stap_arg_array_from_string(Arena *arena, Arch arch, String8 string); + +#endif // STAP_PARSE_H + diff --git a/src/stap/stap_test.c b/src/stap/stap_test.c new file mode 100644 index 00000000..70c31539 --- /dev/null +++ b/src/stap/stap_test.c @@ -0,0 +1,114 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#define BUILD_TITLE "STAP Parser Test" +#define BUILD_CONSOLE_INTERFACE 1 + +#include "base/base_inc.h" +#include "os/os_inc.h" +#include "regs/regs.h" +#include "stap/stap_parse.h" + +#include "base/base_inc.c" +#include "os/os_inc.c" +#include "regs/regs.c" +#include "stap/stap_parse.c" + +internal void +entry_point(CmdLine *cmd_line) +{ + B32 passed = 0; + STAP_Arg arg = {0}; + String8 error = {0}; + String8 string = {0}; + + string = str8_lit("-2@$-123"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_type == STAP_ArgValueType_S); + AssertAlways(arg.value_size == 2); + AssertAlways(arg.type == STAP_ArgType_Imm); + AssertAlways((S64)arg.imm == -123); + + string = str8_lit("1@$43"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_type == STAP_ArgValueType_U); + AssertAlways(arg.value_size == 1); + AssertAlways(arg.type == STAP_ArgType_Imm); + AssertAlways((S64)arg.imm == 43); + + string = str8_lit("4f@%eax"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_type == STAP_ArgValueType_F); + AssertAlways(arg.value_size == 4); + AssertAlways(arg.type == STAP_ArgType_Reg); + AssertAlways(arg.reg.is_alias == 1); + AssertAlways(arg.reg.reg_code == REGS_AliasCodeX64_eax); + + string = str8_lit("4@(%rdi)"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_size == 4); + AssertAlways(arg.value_type == STAP_ArgValueType_U); + AssertAlways(arg.type == STAP_ArgType_MemoryRef); + AssertAlways(arg.memory_ref.disp == 0); + AssertAlways(arg.memory_ref.base.reg_code == REGS_RegCodeX64_rdi); + AssertAlways(!arg.memory_ref.base.is_alias); + AssertAlways(arg.memory_ref.index.reg_code == 0); + AssertAlways(arg.memory_ref.index.is_alias == 0); + AssertAlways(arg.memory_ref.scale == 1); + + string = str8_lit("4@-22(%rdi)"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_size == 4); + AssertAlways(arg.value_type == STAP_ArgValueType_U); + AssertAlways(arg.type == STAP_ArgType_MemoryRef); + AssertAlways(arg.memory_ref.disp == -22); + AssertAlways(arg.memory_ref.base.reg_code == REGS_RegCodeX64_rdi); + AssertAlways(!arg.memory_ref.base.is_alias); + AssertAlways(arg.memory_ref.index.reg_code == 0); + AssertAlways(arg.memory_ref.index.is_alias == 0); + AssertAlways(arg.memory_ref.scale == 1); + + string = str8_lit("4@32(%rax,%rsi,8)"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_size == 4); + AssertAlways(arg.value_type == STAP_ArgValueType_U); + AssertAlways(arg.memory_ref.disp == 32); + AssertAlways(arg.memory_ref.base.reg_code == REGS_RegCodeX64_rax); + AssertAlways(arg.memory_ref.base.is_alias == 0); + AssertAlways(arg.memory_ref.index.reg_code == REGS_RegCodeX64_rsi); + AssertAlways(arg.memory_ref.index.is_alias == 0); + AssertAlways(arg.memory_ref.scale == 8); + + string = str8_lit("4@32(,%rsi,8)"); + error = stap_parse_args_x64(string, &arg); + if (error.size != 0) { goto exit; } + AssertAlways(arg.value_size == 4); + AssertAlways(arg.value_type == STAP_ArgValueType_U); + AssertAlways(arg.memory_ref.disp == 32); + AssertAlways(arg.memory_ref.base.reg_code == 0); + AssertAlways(arg.memory_ref.index.reg_code == REGS_RegCodeX64_rsi); + AssertAlways(arg.memory_ref.scale == 8); + + error = stap_parse_args_x64(str8_lit("4@(,,)"), &arg); + if (error.size == 0) { goto exit; } + + error = stap_parse_args_x64(str8_lit("4@()"), &arg); + if (error.size == 0) { goto exit; } + + error = stap_parse_args_x64(str8_lit("4@(%rdi, %rsi, 8"), &arg); + if (error.size == 0) { goto exit; } + + error = stap_parse_args_x64(str8_lit("4@( ,, 8"), &arg); + if (error.size == 0) { goto exit; } + + passed = 1; +exit:; + AssertAlways(passed); +} +