Python 3.10 Support (#3577)

* Extract major and minor from python version string instead of converting it to double

The patch fixes 3.10 version extraction (3.10 == 3.1)

* update latest python magic version to 3491 in get_code_object

* opcodes for python3.10

* PYC: do not search for strings in code

* PYC python 3.10 tests

* Fixed typo in "pyc 3.9 sections" test

* pyc: refactored string search

* pyc: removed parse_version_major_minor

use "magic_int_within" to check python version
This commit is contained in:
borzacchiello 2023-06-16 02:28:17 +02:00 committed by GitHub
parent 5f14d0e0b5
commit d9a5cf3d28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 295 additions and 86 deletions

View file

@ -220,6 +220,7 @@ rz_analysis_sources = [
'../asm/arch/pyc/opcode_37.c',
'../asm/arch/pyc/opcode_38.c',
'../asm/arch/pyc/opcode_39.c',
'../asm/arch/pyc/opcode_310.c',
'../asm/arch/pyc/opcode_3x.c',
'../asm/arch/pyc/opcode_analysis.c',
'../asm/arch/pyc/opcode_arg_fmt.c',

View file

@ -8,6 +8,8 @@
#include "../../asm/arch/pyc/pyc_dis.h"
#define JMP_OFFSET(ops, v) ((ops)->jump_use_instruction_offset ? (v)*2 : (v))
static int archinfo(RzAnalysis *analysis, RzAnalysisInfoType query) {
if (!strcmp(analysis->cpu, "x86")) {
return -1;
@ -103,7 +105,7 @@ static int pyc_op(RzAnalysis *a, RzAnalysisOp *op, ut64 addr, const ut8 *data, i
if (op_obj->type & HASJABS) {
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = func_base + oparg;
op->jump = func_base + JMP_OFFSET(ops, oparg);
if (op_obj->type & HASCONDITION) {
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
@ -113,7 +115,7 @@ static int pyc_op(RzAnalysis *a, RzAnalysisOp *op, ut64 addr, const ut8 *data, i
}
if (op_obj->type & HASJREL) {
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = addr + oparg + ((is_python36) ? 2 : 3);
op->jump = addr + ((is_python36) ? 2 : 3) + JMP_OFFSET(ops, oparg);
op->fail = addr + ((is_python36) ? 2 : 3);
if (op_obj->type & HASCONDITION) {

View file

@ -145,6 +145,7 @@ static version_opcode version_op[] = {
{ "v3.9.10", opcode_39 },
{ "v3.9.11", opcode_39 },
{ "v3.9.12", opcode_39 },
{ "v3.10.0", opcode_310 },
{ NULL, NULL },
};
@ -188,6 +189,7 @@ pyc_opcodes *new_pyc_opcodes() {
if (!ret) {
return NULL;
}
ret->jump_use_instruction_offset = false;
ret->have_argument = 90;
ret->opcodes = malloc(sizeof(pyc_opcode_object) * 256);
if (!ret->opcodes) {

View file

@ -46,6 +46,7 @@ typedef struct {
ut8 extended_arg;
ut8 have_argument;
ut8 bits;
bool jump_use_instruction_offset;
void *(*version_sig)();
RzList /*<pyc_arg_fmt *>*/ *opcode_arg_fmt;
pyc_opcode_object *opcodes;
@ -95,6 +96,7 @@ pyc_opcodes *opcode_36(void);
pyc_opcodes *opcode_37(void);
pyc_opcodes *opcode_38(void);
pyc_opcodes *opcode_39(void);
pyc_opcodes *opcode_310(void);
pyc_opcodes *get_opcode_by_version(char *version);

View file

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2023 FXTi <lucaborza@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include "opcode.h"
pyc_opcodes *opcode_310(void) {
pyc_opcodes *ret = opcode_39();
if (!ret) {
return NULL;
}
ret->version_sig = (void *(*)())opcode_310;
ret->jump_use_instruction_offset = true;
return ret;
}

View file

@ -207,6 +207,7 @@ rz_asm_sources = [
'arch/pyc/opcode_37.c',
'arch/pyc/opcode_38.c',
'arch/pyc/opcode_39.c',
'arch/pyc/opcode_310.c',
'arch/pyc/opcode_3x.c',
'arch/pyc/opcode_analysis.c',
'arch/pyc/opcode_arg_fmt.c',

View file

@ -424,7 +424,28 @@ static pyc_object *get_string_object(RzBuffer *buffer) {
return ret;
}
static pyc_object *get_unicode_object(RzBuffer *buffer) {
static bool add_string_to_cache(RzBinPycObj *pyc, ut64 addr, const char *data, ut32 size, ut32 length, RzStrEnc type) {
if (size == 0) {
return true;
}
RzBinString *string = RZ_NEW0(RzBinString);
if (!string) {
return false;
}
string->paddr = string->vaddr = addr;
string->size = size;
string->length = length;
string->ordinal = 0;
string->type = type;
string->string = rz_str_new(data);
if (!rz_list_append(pyc->strings_cache, string)) {
return false;
}
return true;
}
static pyc_object *get_unicode_object(RzBinPycObj *pyc, RzBuffer *buffer) {
pyc_object *ret = NULL;
bool error = false;
ut32 n = 0;
@ -437,6 +458,7 @@ static pyc_object *get_unicode_object(RzBuffer *buffer) {
if (error) {
return NULL;
}
ut64 addr = rz_buf_tell(buffer);
ret = RZ_NEW0(pyc_object);
ret->type = TYPE_UNICODE;
ret->data = get_bytes(buffer, n);
@ -444,6 +466,11 @@ static pyc_object *get_unicode_object(RzBuffer *buffer) {
RZ_FREE(ret);
return NULL;
}
if (!add_string_to_cache(pyc, addr, ret->data, n, rz_utf8_strlen(ret->data), RZ_STRING_ENC_UTF8)) {
RZ_FREE(ret);
return NULL;
}
return ret;
}
@ -627,22 +654,29 @@ static pyc_object *get_set_object(RzBinPycObj *pyc, RzBuffer *buffer) {
return ret;
}
static pyc_object *get_ascii_object_generic(RzBuffer *buffer, ut32 size, bool interned) {
static pyc_object *get_ascii_object_generic(RzBinPycObj *pyc, RzBuffer *buffer, ut32 size, bool interned) {
pyc_object *ret = NULL;
ret = RZ_NEW0(pyc_object);
if (!ret) {
return NULL;
}
ut64 addr = rz_buf_tell(buffer);
ret->type = TYPE_ASCII;
ret->data = get_bytes(buffer, size);
if (!ret->data) {
RZ_FREE(ret);
}
if (!add_string_to_cache(pyc, addr, ret->data, size, size, RZ_STRING_ENC_8BIT)) {
RZ_FREE(ret);
return NULL;
}
return ret;
}
static pyc_object *get_ascii_object(RzBuffer *buffer) {
static pyc_object *get_ascii_object(RzBinPycObj *pyc, RzBuffer *buffer) {
bool error = false;
ut32 n = 0;
@ -650,10 +684,10 @@ static pyc_object *get_ascii_object(RzBuffer *buffer) {
if (error) {
return NULL;
}
return get_ascii_object_generic(buffer, n, true);
return get_ascii_object_generic(pyc, buffer, n, true);
}
static pyc_object *get_ascii_interned_object(RzBuffer *buffer) {
static pyc_object *get_ascii_interned_object(RzBinPycObj *pyc, RzBuffer *buffer) {
bool error = false;
ut32 n;
@ -661,10 +695,10 @@ static pyc_object *get_ascii_interned_object(RzBuffer *buffer) {
if (error) {
return NULL;
}
return get_ascii_object_generic(buffer, n, true);
return get_ascii_object_generic(pyc, buffer, n, true);
}
static pyc_object *get_short_ascii_object(RzBuffer *buffer) {
static pyc_object *get_short_ascii_object(RzBinPycObj *pyc, RzBuffer *buffer) {
bool error = false;
ut8 n;
@ -672,10 +706,10 @@ static pyc_object *get_short_ascii_object(RzBuffer *buffer) {
if (error) {
return NULL;
}
return get_ascii_object_generic(buffer, n, false);
return get_ascii_object_generic(pyc, buffer, n, false);
}
static pyc_object *get_short_ascii_interned_object(RzBuffer *buffer) {
static pyc_object *get_short_ascii_interned_object(RzBinPycObj *pyc, RzBuffer *buffer) {
bool error = false;
ut8 n;
@ -683,7 +717,7 @@ static pyc_object *get_short_ascii_interned_object(RzBuffer *buffer) {
if (error) {
return NULL;
}
return get_ascii_object_generic(buffer, n, true);
return get_ascii_object_generic(pyc, buffer, n, true);
}
static pyc_object *get_ref_object(RzBinPycObj *pyc, RzBuffer *buffer) {
@ -866,7 +900,7 @@ static pyc_object *get_code_object(RzBinPycObj *pyc, RzBuffer *buffer) {
bool v15_to_22 = magic_int_within(pyc->magic_int, 20121, 60718, &error); // 1.5a1 - 2.2a1
bool v13_to_20 = magic_int_within(pyc->magic_int, 11913, 50824, &error); // 1.3b1 - 2.0b1
// bool v21_to_27 = (!v13_to_20) && magic_int_within (magic_int, 60124, 62212, &error);
bool has_posonlyargcount = magic_int_within(pyc->magic_int, 3410, 3424, &error); // v3.8.0a4 - latest
bool has_posonlyargcount = magic_int_within(pyc->magic_int, 3410, 3491, &error); // v3.8.0a4 - latest
if (error) {
free(ret);
free(cobj);
@ -1051,16 +1085,16 @@ static pyc_object *get_object(RzBinPycObj *pyc, RzBuffer *buffer) {
ret = get_int_object(buffer);
break;
case TYPE_ASCII_INTERNED:
ret = get_ascii_interned_object(buffer);
ret = get_ascii_interned_object(pyc, buffer);
break;
case TYPE_SHORT_ASCII:
ret = get_short_ascii_object(buffer);
ret = get_short_ascii_object(pyc, buffer);
break;
case TYPE_ASCII:
ret = get_ascii_object(buffer);
ret = get_ascii_object(pyc, buffer);
break;
case TYPE_SHORT_ASCII_INTERNED:
ret = get_short_ascii_interned_object(buffer);
ret = get_short_ascii_interned_object(pyc, buffer);
break;
case TYPE_INT64:
ret = get_int64_object(buffer);
@ -1090,7 +1124,7 @@ static pyc_object *get_object(RzBinPycObj *pyc, RzBuffer *buffer) {
ret = get_long_object(buffer);
break;
case TYPE_UNICODE:
ret = get_unicode_object(buffer);
ret = get_unicode_object(pyc, buffer);
break;
case TYPE_DICT:
ret = get_dict_object(pyc, buffer);
@ -1151,6 +1185,7 @@ static bool extract_sections_symbols(RzBinPycObj *pyc, pyc_object *obj, RzList /
if (!section->name) {
goto fail;
}
section->has_strings = false;
section->paddr = cobj->start_offset;
section->vaddr = cobj->start_offset;
section->size = cobj->end_offset - cobj->start_offset;

View file

@ -79,6 +79,8 @@ typedef struct pyc_context {
/* used from marshall.c */
RzList /*<char *>*/ *interned_table;
RzList /*<RzBinSection *>*/ *sections_cache;
RzList /*<RzBinString *>*/ *strings_cache;
RzList /*<RzBinSymbol *>*/ *symbols_cache;
RzList /*<RzList<void *> *>*/ *shared;
RzList /*<pyc_object *>*/ *refs; // If you don't have a good reason, do not change this. And also checkout !refs in get_code_object()
ut32 magic_int;

View file

@ -312,20 +312,3 @@ bool magic_int_within(ut32 target_magic, ut32 lower, ut32 upper, bool *error) {
return (li <= ti) && (ti <= ui);
}
double version2double(const char *version) {
unsigned idx = 0, buf_idx = 0;
char buf[20];
double result;
while (!('0' <= version[idx] && version[idx] <= '9'))
idx++;
for (; version[idx] != '.'; idx++)
buf[buf_idx++] = version[idx];
buf[buf_idx++] = version[idx++];
for (; '0' <= version[idx] && version[idx] <= '9'; idx++)
buf[buf_idx++] = version[idx];
buf[buf_idx] = '\x00';
sscanf(buf, "%lf", &result);
return result;
}

View file

@ -15,8 +15,6 @@ struct pyc_version {
struct pyc_version get_pyc_version(ut32 magic);
double version2double(const char *version);
bool magic_int_within(ut32 target_magic, ut32 lower, ut32 uppper, bool *error);
#endif

View file

@ -15,6 +15,50 @@ static bool check_buffer(RzBuffer *b) {
return false;
}
static bool init_pyc_cache(RzBinPycObj *pyc, RzBuffer *buf) {
RzList *shared = rz_list_newf((RzListFree)rz_list_free);
if (!shared) {
return false;
}
RzList *cobjs = rz_list_newf((RzListFree)free);
if (!cobjs) {
rz_list_free(shared);
return false;
}
pyc->interned_table = rz_list_newf((RzListFree)free);
if (!pyc->interned_table) {
rz_list_free(shared);
rz_list_free(cobjs);
return false;
}
rz_list_append(shared, cobjs);
rz_list_append(shared, pyc->interned_table);
pyc->shared = shared;
RzList *sections = rz_list_newf((RzListFree)free);
if (!sections) {
rz_list_free(shared);
return false;
}
pyc->sections_cache = sections;
RzList *symbols = rz_list_newf((RzListFree)free);
if (!symbols) {
rz_list_free(shared);
rz_list_free(sections);
return false;
}
pyc->symbols_cache = symbols;
RzList *strings = rz_list_newf((RzListFree)free);
if (!strings) {
rz_list_free(shared);
return false;
}
pyc->strings_cache = strings;
rz_buf_seek(buf, pyc->code_start_offset, RZ_BUF_SET);
pyc_get_sections_symbols(pyc, sections, symbols, cobjs, buf, pyc->version.magic);
return true;
}
static bool load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *buf, Sdb *sdb) {
RzBinPycObj *ctx = RZ_NEW0(RzBinPycObj);
if (!ctx) {
@ -53,6 +97,13 @@ static RzBinInfo *info(RzBinFile *arch) {
if (!ret) {
return NULL;
}
bool error;
bool is_before_py_36 = magic_int_within(ctx->version.magic, 0x9494, 0x0d16, &error);
if (error) {
return NULL;
}
ret->file = strdup(arch->file);
ret->type = rz_str_newf("Python %s byte-compiled file", ctx->version.version);
ret->bclass = strdup("Python byte-compiled file");
@ -61,17 +112,14 @@ static RzBinInfo *info(RzBinFile *arch) {
ret->machine = rz_str_newf("Python %s VM (rev %s)", ctx->version.version,
ctx->version.revision);
ret->os = strdup("any");
ret->bits = version2double(ctx->version.version) < 3.6 ? 16 : 8;
ret->bits = is_before_py_36 ? 16 : 8;
ret->cpu = strdup(ctx->version.version); // pass version info in cpu, Asm plugin will get it
return ret;
}
static RzList /*<RzBinSection *>*/ *sections(RzBinFile *arch) {
RzBinPycObj *ctx = arch->o->bin_obj;
return ctx->sections_cache;
}
static RzList /*<RzBinAddr *>*/ *entries(RzBinFile *arch) {
RzBinPycObj *pyc = arch->o->bin_obj;
RzList *entries = rz_list_newf((RzListFree)free);
if (!entries) {
return NULL;
@ -86,6 +134,11 @@ static RzList /*<RzBinAddr *>*/ *entries(RzBinFile *arch) {
addr->vaddr = entrypoint;
rz_buf_seek(arch->buf, entrypoint, RZ_IO_SEEK_SET);
rz_list_append(entries, addr);
if (!init_pyc_cache(pyc, arch->buf)) {
rz_list_free(entries);
return NULL;
}
return entries;
}
@ -93,42 +146,19 @@ static ut64 baddr(RzBinFile *bf) {
return 0;
}
static RzList /*<RzBinSection *>*/ *sections(RzBinFile *arch) {
RzBinPycObj *ctx = arch->o->bin_obj;
return ctx->sections_cache;
}
static RzList /*<RzBinSymbol *>*/ *symbols(RzBinFile *arch) {
RzBinPycObj *pyc = arch->o->bin_obj;
RzList *shared = rz_list_newf((RzListFree)rz_list_free);
if (!shared) {
return NULL;
}
RzList *cobjs = rz_list_newf((RzListFree)free);
if (!cobjs) {
rz_list_free(shared);
return NULL;
}
pyc->interned_table = rz_list_newf((RzListFree)free);
if (!pyc->interned_table) {
rz_list_free(shared);
rz_list_free(cobjs);
return NULL;
}
rz_list_append(shared, cobjs);
rz_list_append(shared, pyc->interned_table);
pyc->shared = shared;
RzList *sections = rz_list_newf((RzListFree)free);
if (!sections) {
rz_list_free(shared);
return NULL;
}
RzList *symbols = rz_list_newf((RzListFree)free);
if (!symbols) {
rz_list_free(shared);
rz_list_free(sections);
return NULL;
}
RzBuffer *buffer = arch->buf;
rz_buf_seek(buffer, pyc->code_start_offset, RZ_BUF_SET);
pyc_get_sections_symbols(pyc, sections, symbols, cobjs, buffer, pyc->version.magic);
pyc->sections_cache = sections;
return symbols;
return pyc->symbols_cache;
}
static RzList /*<RzBinString *>*/ *strings(RzBinFile *bf) {
RzBinPycObj *pyc = bf->o->bin_obj;
return pyc->strings_cache;
}
static void destroy(RzBinFile *bf) {
@ -148,6 +178,7 @@ RzBinPlugin rz_bin_plugin_pyc = {
.sections = &sections,
.baddr = &baddr,
.symbols = &symbols,
.strings = &strings,
.destroy = &destroy
};

View file

@ -1,3 +1,19 @@
NAME=pyc load version310
FILE=bins/pyc/py310.pyc
CMDS=<<EOF
iI~machine
EOF
EXPECT=<<EOF
machine Python v3.10.0 VM (rev bfb376ffcc4260feb9bf1b9a110559b1ff31da80)
EOF
REGEXP_FILTER_ERR=<<EOF
free_object\ \(0\)
EOF
EXPECT_ERR=<<EOF
free_object (0)
EOF
RUN
NAME=pyc load version39
FILE=bins/pyc/py39.pyc
CMDS=<<EOF
@ -94,19 +110,41 @@ free_object (0)
EOF
RUN
NAME=pyc 3.9 sections
FILE=bins/pyc/py37.pyc
NAME=pyc 3.10 sections
FILE=bins/pyc/py310.pyc
CMDS=<<EOF
iS~Bat
EOF
EXPECT=<<EOF
0x00001f0d 0x2a 0x00001f0d 0x2a 0x0 ---- module_.Bat
0x00001f5f 0xa 0x00001f5f 0xa 0x0 ---- module_.Bat.__init
0x00001fca 0x8 0x00001fca 0x8 0x0 ---- module_.Bat.say
0x00002037 0x4 0x00002037 0x4 0x0 ---- module_.Bat.sonar
0x00002103 0x1c 0x00002103 0x1c 0x0 ---- module_.Batman
0x00002143 0x44 0x00002143 0x44 0x0 ---- module_.Batman.__init
0x00002235 0x4 0x00002235 0x4 0x0 ---- module_.Batman.sing
0x0000201d 0x2a 0x0000201d 0x2a 0x0 ---- module_.Bat
0x00002073 0x0 0x00002073 0x0 0x0 ---- module_.Bat.__init
0x000020da 0x8 0x000020da 0x8 0x0 ---- module_.Bat.say
0x00002144 0x0 0x00002144 0x0 0x0 ---- module_.Bat.sonar
0x0000220e 0x1c 0x0000220e 0x1c 0x0 ---- module_.Batman
0x00002252 0x48 0x00002252 0x48 0x0 ---- module_.Batman.__init
0x00002356 0x0 0x00002356 0x0 0x0 ---- module_.Batman.sing
EOF
REGEXP_FILTER_ERR=<<EOF
free_object\ \(0\)
EOF
EXPECT_ERR=<<EOF
free_object (0)
EOF
RUN
NAME=pyc 3.9 sections
FILE=bins/pyc/py39.pyc
CMDS=<<EOF
iS~Bat
EOF
EXPECT=<<EOF
0x00001fdf 0x2a 0x00001fdf 0x2a 0x0 ---- module_.Bat
0x00002035 0xa 0x00002035 0xa 0x0 ---- module_.Bat.__init
0x000020a6 0x8 0x000020a6 0x8 0x0 ---- module_.Bat.say
0x00002110 0x4 0x00002110 0x4 0x0 ---- module_.Bat.sonar
0x000021de 0x1c 0x000021de 0x1c 0x0 ---- module_.Batman
0x00002222 0x48 0x00002222 0x48 0x0 ---- module_.Batman.__init
0x00002326 0x4 0x00002326 0x4 0x0 ---- module_.Batman.sing
EOF
REGEXP_FILTER_ERR=<<EOF
free_object\ \(0\)
@ -164,6 +202,70 @@ free_object (0)
EOF
RUN
NAME=pyc 3.10 disasm
FILE=bins/pyc/py310.pyc
CMDS=<<EOF
pd 10
EOF
EXPECT=<<EOF
;-- entry0:
;-- section.module:
;-- <module>:
0x0000002e LOAD_CONST Multiline strings can be written
using three "s, and are often used
as documentation. ; [00] ---- section size 3292 named module
0x00000030 STORE_NAME __doc__
0x00000032 NOP
0x00000034 NOP
0x00000036 NOP
0x00000038 NOP
0x0000003a NOP
0x0000003c NOP
0x0000003e NOP
0x00000040 NOP
EOF
REGEXP_FILTER_ERR=<<EOF
free_object\ \(0\)
EOF
EXPECT_ERR=<<EOF
free_object (0)
EOF
RUN
NAME=pyc 3.10 disasm
FILE=bins/pyc/py310.pyc
CMDS=<<EOF
s 0x00001eb3
pd 16
EOF
EXPECT=<<EOF
;-- section.module_.Superhero.boast:
;-- <module>.Superhero.boast:
0x00001eb3 LOAD_FAST self ; [29] ---- section size 32 named module_.Superhero.boast
0x00001eb5 LOAD_ATTR superpowers
0x00001eb7 GET_ITER
,,.-> 0x00001eb9 FOR_ITER 10
`---> 0x00001ebb STORE_FAST power
|: 0x00001ebd LOAD_GLOBAL print
|: 0x00001ebf LOAD_CONST I wield the power of {pow}!
|: 0x00001ec1 LOAD_ATTR format
|: 0x00001ec3 LOAD_FAST power
|: 0x00001ec5 LOAD_CONST (pow)
|: 0x00001ec7 CALL_FUNCTION_KW 1 total positional and keyword args
|: 0x00001ec9 CALL_FUNCTION 1
|: 0x00001ecb POP_TOP
|`=< 0x00001ecd JUMP_ABSOLUTE 3
`--> 0x00001ecf LOAD_CONST None
0x00001ed1 RETURN_VALUE
EOF
REGEXP_FILTER_ERR=<<EOF
free_object\ \(0\)
EOF
EXPECT_ERR=<<EOF
free_object (0)
EOF
RUN
NAME=pyc 3.9 disasm
FILE=bins/pyc/py39.pyc
CMDS=<<EOF
@ -194,6 +296,40 @@ free_object (0)
EOF
RUN
NAME=pyc 3.9 disasm
FILE=bins/pyc/py39.pyc
CMDS=<<EOF
s 0x00001e79
pd 16
EOF
EXPECT=<<EOF
;-- section.module_.Superhero.boast:
;-- <module>.Superhero.boast:
0x00001e79 LOAD_FAST self ; [29] ---- section size 32 named module_.Superhero.boast
0x00001e7b LOAD_ATTR superpowers
0x00001e7d GET_ITER
,,.-> 0x00001e7f FOR_ITER 20
`---> 0x00001e81 STORE_FAST power
|: 0x00001e83 LOAD_GLOBAL print
|: 0x00001e85 LOAD_CONST I wield the power of {pow}!
|: 0x00001e87 LOAD_ATTR format
|: 0x00001e89 LOAD_FAST power
|: 0x00001e8b LOAD_CONST (pow)
|: 0x00001e8d CALL_FUNCTION_KW 1 total positional and keyword args
|: 0x00001e8f CALL_FUNCTION 1
|: 0x00001e91 POP_TOP
|`=< 0x00001e93 JUMP_ABSOLUTE 6
`--> 0x00001e95 LOAD_CONST None
0x00001e97 RETURN_VALUE
EOF
REGEXP_FILTER_ERR=<<EOF
free_object\ \(0\)
EOF
EXPECT_ERR=<<EOF
free_object (0)
EOF
RUN
NAME=pyc 3.7 disasm
FILE=bins/pyc/py37.pyc
CMDS=<<EOF