Trace FIFA17 PMA producer lifecycle
This commit is contained in:
Executable
+394
@@ -0,0 +1,394 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Trace FIFA17's PMA ScenarioModeStart-to-event-5 producer chain.
|
||||
|
||||
The generated GDB program uses hardware breakpoints, only reads registers and
|
||||
client memory, logs, and continues. Breakpoints are rotated so no more than four
|
||||
are enabled. It never calls client functions, writes client memory, emits an
|
||||
event, or drives input.
|
||||
|
||||
pma_producer_trace.py [pid] [--variant mode0|alternate] [--output PATH]
|
||||
pma_producer_trace.py --selftest
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import match_advance_trace as advance
|
||||
import match_transition_trace as transition
|
||||
|
||||
VARIANTS = {
|
||||
"mode0": {
|
||||
"scenario_rva": 0x07B1C190,
|
||||
"writer_rva": 0x07B1C26B,
|
||||
"register_rva": 0x07B1C282,
|
||||
"writer_context": "$rsi",
|
||||
"writer_async_requested": "1",
|
||||
"arm_condition": "1",
|
||||
},
|
||||
"alternate": {
|
||||
"scenario_rva": 0x07B1C050,
|
||||
"writer_rva": 0x07B1C12F,
|
||||
"register_rva": 0x07B1C146,
|
||||
"writer_context": "$rbp",
|
||||
"writer_async_requested": "$sil",
|
||||
"arm_condition": "$tracked_ctx != 0 && $rcx == $tracked_ctx",
|
||||
},
|
||||
}
|
||||
PMA_COMPLETION_ARM_RVA = 0x07B1AE60
|
||||
PMA_COMPLETION_ARM_WRITER_RVA = 0x07B1AF33
|
||||
ASYNC_COMPLETION_RVA = 0x07B046C0
|
||||
CALLBACK_DISPATCHER_RVA = 0x07AC87B0
|
||||
PMA_INSTRUCTIONS_HANDLER_RVA = 0x07AC91E0
|
||||
GAMEPLAY_GLOBAL_RVA = 0x04BFB910
|
||||
PMA_INSTRUCTIONS_VTABLE_RVA = 0x03AF2750
|
||||
|
||||
|
||||
def addresses(base: int, variant: str) -> dict[str, int]:
|
||||
config = VARIANTS[variant]
|
||||
return {
|
||||
"scenario": base + config["scenario_rva"],
|
||||
"writer": base + config["writer_rva"],
|
||||
"register": base + config["register_rva"],
|
||||
"arm": base + PMA_COMPLETION_ARM_RVA,
|
||||
"arm_writer": base + PMA_COMPLETION_ARM_WRITER_RVA,
|
||||
"completion": base + ASYNC_COMPLETION_RVA,
|
||||
"dispatcher": base + CALLBACK_DISPATCHER_RVA,
|
||||
"instructions": base + PMA_INSTRUCTIONS_HANDLER_RVA,
|
||||
"gameplay_global": base + GAMEPLAY_GLOBAL_RVA,
|
||||
"instructions_vtable": base + PMA_INSTRUCTIONS_VTABLE_RVA,
|
||||
}
|
||||
|
||||
|
||||
def gdb_prelude(pid: int, output: str) -> str:
|
||||
if any(character in output for character in "\n\r"):
|
||||
raise ValueError("output path cannot contain a newline")
|
||||
return f"""set pagination off
|
||||
set confirm off
|
||||
set print thread-events off
|
||||
set breakpoint always-inserted off
|
||||
set logging file {output}
|
||||
set logging overwrite on
|
||||
set logging redirect off
|
||||
set logging enabled on
|
||||
handle SIGSEGV nostop noprint pass
|
||||
handle SIGILL nostop noprint pass
|
||||
handle SIGFPE nostop noprint pass
|
||||
handle SIGPIPE nostop noprint pass
|
||||
handle SIGALRM nostop noprint pass
|
||||
handle SIGUSR1 nostop noprint pass
|
||||
handle SIGUSR2 nostop noprint pass
|
||||
|
||||
attach {pid}
|
||||
"""
|
||||
|
||||
|
||||
def build_script(pid: int, fifa_base: int, output: str, variant: str) -> str:
|
||||
address = addresses(fifa_base, variant)
|
||||
config = VARIANTS[variant]
|
||||
return (
|
||||
gdb_prelude(pid, output)
|
||||
+ f"""define snapshot_pma_context
|
||||
set $snap_ctx = $arg0
|
||||
set $snap_flag40 = -1
|
||||
set $snap_callback_vtable = 0
|
||||
set $snap_callback_owner = 0
|
||||
set $snap_dispatcher = 0
|
||||
set $snap_dispatcher_vtable = 0
|
||||
set $snap_pma = 0
|
||||
set $snap_pma_flag18 = -1
|
||||
set $snap_pma_parent = 0
|
||||
set $snap_pma_machine = 0
|
||||
set $snap_pma_current = 0
|
||||
if $snap_ctx != 0
|
||||
set $snap_flag40 = *(unsigned char*)($snap_ctx+0x40)
|
||||
set $snap_callback_vtable = *(void**)($snap_ctx+0x48)
|
||||
set $snap_callback_owner = *(void**)($snap_ctx+0x78)
|
||||
set $snap_dispatcher = $snap_ctx+0x80
|
||||
set $snap_dispatcher_vtable = *(void**)$snap_dispatcher
|
||||
set $snap_sentinel = $snap_ctx+0x88
|
||||
set $snap_node = *(void**)$snap_sentinel
|
||||
set $snap_scan = 0
|
||||
while $snap_node != 0 && $snap_node != $snap_sentinel && $snap_scan < 8
|
||||
set $snap_candidate = *(void**)($snap_node+0x10)
|
||||
if $snap_candidate != 0
|
||||
if *(void**)$snap_candidate == 0x{address['instructions_vtable']:x}
|
||||
set $snap_pma = $snap_candidate
|
||||
end
|
||||
end
|
||||
set $snap_node = *(void**)$snap_node
|
||||
set $snap_scan = $snap_scan+1
|
||||
end
|
||||
if $snap_pma != 0
|
||||
set $snap_pma_flag18 = *(unsigned char*)($snap_pma+0x18)
|
||||
set $snap_pma_parent = *(void**)($snap_pma+0x8)
|
||||
if $snap_pma_parent != 0
|
||||
set $snap_pma_machine = *(void**)($snap_pma_parent+0x8)
|
||||
end
|
||||
if $snap_pma_machine != 0
|
||||
set $snap_pma_current = *(void**)($snap_pma_machine+0x10)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
define snapshot_gameplay
|
||||
set $snap_gameplay_global = *(void**)0x{address['gameplay_global']:x}
|
||||
set $snap_listener_manager = 0
|
||||
set $snap_listener_table = 0
|
||||
set $snap_listener_index = -1
|
||||
set $snap_free_roam = 0
|
||||
set $snap_free_roam_state = -1
|
||||
set $snap_free_roam_111 = -1
|
||||
set $snap_free_roam_112 = -1
|
||||
set $snap_free_roam_124 = -1
|
||||
set $snap_selected = 0
|
||||
set $snap_selected_vtable = 0
|
||||
set $snap_selected_mode = -1
|
||||
if $snap_gameplay_global != 0
|
||||
set $snap_listener_manager = *(void**)($snap_gameplay_global+0x58)
|
||||
end
|
||||
if $snap_listener_manager != 0
|
||||
set $snap_listener_table = *(void**)$snap_listener_manager
|
||||
end
|
||||
if $snap_listener_table != 0
|
||||
set $snap_free_roam = *(void**)$snap_listener_table
|
||||
set $snap_listener_index = *(int*)($snap_listener_table+0x20)
|
||||
if $snap_listener_index >= 0 && $snap_listener_index < 3
|
||||
set $snap_selected = *(void**)($snap_listener_table+$snap_listener_index*8)
|
||||
end
|
||||
end
|
||||
if $snap_free_roam != 0
|
||||
set $snap_free_roam_state = *(int*)($snap_free_roam+0x30)
|
||||
set $snap_free_roam_111 = *(unsigned char*)($snap_free_roam+0x111)
|
||||
set $snap_free_roam_112 = *(unsigned char*)($snap_free_roam+0x112)
|
||||
set $snap_free_roam_124 = *(int*)($snap_free_roam+0x124)
|
||||
end
|
||||
if $snap_selected != 0
|
||||
set $snap_selected_vtable = *(void**)$snap_selected
|
||||
set $snap_selected_mode = *(int*)($snap_selected+0x18)
|
||||
end
|
||||
end
|
||||
set $tracked_ctx = 0
|
||||
|
||||
|
||||
hbreak *0x{address['scenario']:x}
|
||||
commands
|
||||
silent
|
||||
set $ctx = $rcx
|
||||
set $tracked_ctx = $ctx
|
||||
snapshot_pma_context $ctx
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d SCENARIO_MODE_START" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d function=%p caller_return=%p ctx=%p ctx_vtable=%p arg_descriptor=%p arg_scenario=%p async_requested=%d flag40=%d callback_vtable=%p callback_owner=%p dispatcher=%p dispatcher_vtable=%p pma=%p pma_flag18=%d pma_parent=%p pma_machine=%p pma_current=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $ctx, *(void**)$ctx, $rdx, $r8, $r9b, $snap_flag40, $snap_callback_vtable, $snap_callback_owner, $snap_dispatcher, $snap_dispatcher_vtable, $snap_pma, $snap_pma_flag18, $snap_pma_parent, $snap_pma_machine, $snap_pma_current, $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
disable 1
|
||||
enable 2
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['writer']:x}
|
||||
condition 2 $tracked_ctx != 0 && {config['writer_context']} == $tracked_ctx
|
||||
disable 2
|
||||
commands
|
||||
silent
|
||||
set $ctx = {config['writer_context']}
|
||||
snapshot_pma_context $ctx
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d CONTEXT_ARM_WRITER" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d instruction=%p caller_return=%p ctx=%p original_async_requested=%d flag40_before=%d callback_vtable=%p callback_owner_before=%p dispatcher=%p dispatcher_vtable=%p pma=%p pma_flag18=%d pma_current=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $ctx, {config['writer_async_requested']}, $snap_flag40, $snap_callback_vtable, $snap_callback_owner, $snap_dispatcher, $snap_dispatcher_vtable, $snap_pma, $snap_pma_flag18, $snap_pma_current, $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
disable 2
|
||||
enable 3
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['register']:x}
|
||||
condition 3 $tracked_ctx != 0 && $rdx == $tracked_ctx+0x48
|
||||
disable 3
|
||||
commands
|
||||
silent
|
||||
set $callback = $rdx
|
||||
set $ctx = $callback-0x48
|
||||
snapshot_pma_context $ctx
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d ASYNC_REGISTER_CALL" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d callsite=%p caller_return=%p service=%p service_vtable=%p callback=%p callback_vtable=%p ctx=%p flag40=%d callback_owner=%p dispatcher=%p dispatcher_vtable=%p\\n", $_thread, $pc, *(void**)$rsp, $rcx, *(void**)$rcx, $callback, *(void**)$callback, $ctx, $snap_flag40, $snap_callback_owner, $snap_dispatcher, $snap_dispatcher_vtable
|
||||
disable 3
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['arm']:x}
|
||||
condition 4 {config['arm_condition']}
|
||||
commands
|
||||
silent
|
||||
set $ctx = $rcx
|
||||
if $tracked_ctx == 0
|
||||
set $tracked_ctx = $ctx
|
||||
end
|
||||
snapshot_pma_context $ctx
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d COMPLETION_ARM_ENTRY" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d function=%p caller_return=%p ctx=%p ctx_vtable=%p flag40_before=%d callback_vtable=%p callback_owner=%p dispatcher=%p dispatcher_vtable=%p result_source=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $ctx, *(void**)$ctx, $snap_flag40, $snap_callback_vtable, $snap_callback_owner, $snap_dispatcher, $snap_dispatcher_vtable, *(void**)($ctx+0xa8), $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
disable 4
|
||||
enable 5
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['arm_writer']:x}
|
||||
condition 5 $tracked_ctx != 0 && $rsi == $tracked_ctx
|
||||
disable 5
|
||||
commands
|
||||
silent
|
||||
set $ctx = $rsi
|
||||
snapshot_pma_context $ctx
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d COMPLETION_ARM_WRITER" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d instruction=%p caller_return=%p ctx=%p flag40_before=%d result_object=%p result_state28=%d callback_owner=%p dispatcher=%p dispatcher_vtable=%p pma=%p pma_flag18=%d pma_current=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $ctx, $snap_flag40, $rax, *(int*)($rax+0x28), $snap_callback_owner, $snap_dispatcher, $snap_dispatcher_vtable, $snap_pma, $snap_pma_flag18, $snap_pma_current, $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
disable 5
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['completion']:x}
|
||||
condition 6 $tracked_ctx != 0 && $rcx == $tracked_ctx+0x48
|
||||
commands
|
||||
silent
|
||||
set $callback = $rcx
|
||||
set $ctx = *(void**)($callback+0x30)
|
||||
snapshot_pma_context $ctx
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d ASYNC_COMPLETION" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d function=%p caller_return=%p callback=%p callback_vtable=%p ctx=%p callback_matches_ctx48=%d flag40_before=%d arg_rdx=%p arg_r8=%p arg_r9=%p dispatcher=%p dispatcher_vtable=%p pma=%p pma_flag18=%d pma_current=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $callback, *(void**)$callback, $ctx, $callback == $ctx+0x48, $snap_flag40, $rdx, $r8, $r9, $snap_dispatcher, $snap_dispatcher_vtable, $snap_pma, $snap_pma_flag18, $snap_pma_current, $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['dispatcher']:x}
|
||||
condition 7 $tracked_ctx != 0 && $rcx == $tracked_ctx+0x80 && $edx == 5
|
||||
commands
|
||||
silent
|
||||
set $ctx = $rcx-0x80
|
||||
snapshot_pma_context $ctx
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d DISPATCHER_EVENT_5" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d function=%p caller_return=%p dispatcher=%p event=%d arg_r8=%p arg_r9=%p ctx=%p flag40=%d callback_owner=%p pma=%p pma_flag18=%d pma_current=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $rcx, $edx, $r8, $r9, $ctx, $snap_flag40, $snap_callback_owner, $snap_pma, $snap_pma_flag18, $snap_pma_current, $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
disable 7
|
||||
enable 8
|
||||
continue
|
||||
end
|
||||
|
||||
hbreak *0x{address['instructions']:x}
|
||||
disable 8
|
||||
commands
|
||||
silent
|
||||
set $listener = $rcx
|
||||
set $parent = *(void**)($listener+0x8)
|
||||
set $machine = 0
|
||||
set $current = 0
|
||||
if $parent != 0
|
||||
set $machine = *(void**)($parent+0x8)
|
||||
end
|
||||
if $machine != 0
|
||||
set $current = *(void**)($machine+0x10)
|
||||
end
|
||||
snapshot_gameplay
|
||||
python import time; print("PMAPRODUCER epoch_ns=%d mono_ns=%d INSTRUCTIONS_AFTER_EVENT_5" % (time.time_ns(), time.monotonic_ns()), end=" ")
|
||||
printf "thread=%d handler=%p caller_return=%p listener=%p listener_vtable=%p event=%d flag18=%d parent=%p machine=%p current=%p current_vtable=%p free_roam=%p free_state=%d free111=%d free112=%d free124=%d selected_index=%d selected=%p selected_vtable=%p selected_mode=%d\\n", $_thread, $pc, *(void**)$rsp, $listener, *(void**)$listener, $edx, *(unsigned char*)($listener+0x18), $parent, $machine, $current, $current ? *(void**)$current : 0, $snap_free_roam, $snap_free_roam_state, $snap_free_roam_111, $snap_free_roam_112, $snap_free_roam_124, $snap_listener_index, $snap_selected, $snap_selected_vtable, $snap_selected_mode
|
||||
disable 6
|
||||
continue
|
||||
end
|
||||
|
||||
printf "PMAPRODUCER ARMED pid={pid} variant={variant} scenario=0x{address['scenario']:x} writer=0x{address['writer']:x} register=0x{address['register']:x} arm=0x{address['arm']:x} arm_writer=0x{address['arm_writer']:x} completion=0x{address['completion']:x} dispatcher=0x{address['dispatcher']:x} instructions=0x{address['instructions']:x}\\n"
|
||||
continue
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def effective_environment(pid: int) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for item in Path(f"/proc/{pid}/environ").read_bytes().split(b"\0"):
|
||||
if not item.startswith(b"OPENFUT_FIFA17_"):
|
||||
continue
|
||||
key, _, value = item.decode("utf-8", errors="replace").partition("=")
|
||||
values[key] = value
|
||||
return values
|
||||
|
||||
|
||||
def selftest() -> None:
|
||||
mode0 = addresses(0x140000000, "mode0")
|
||||
alternate = addresses(0x140000000, "alternate")
|
||||
script = build_script(1234, 0x140000000, "/tmp/pma-producer.log", "mode0")
|
||||
assert mode0["scenario"] == 0x147B1C190
|
||||
assert mode0["writer"] == 0x147B1C26B
|
||||
assert mode0["register"] == 0x147B1C282
|
||||
assert alternate["scenario"] == 0x147B1C050
|
||||
assert alternate["writer"] == 0x147B1C12F
|
||||
assert alternate["register"] == 0x147B1C146
|
||||
assert mode0["completion"] == 0x147B046C0
|
||||
assert mode0["dispatcher"] == 0x147AC87B0
|
||||
assert mode0["instructions"] == 0x147AC91E0
|
||||
assert mode0["arm"] == 0x147B1AE60
|
||||
assert mode0["arm_writer"] == 0x147B1AF33
|
||||
assert script.count("hbreak *") == 8
|
||||
assert "condition 7 $tracked_ctx != 0" in script
|
||||
assert "disable 2" in script and "enable 2" in script
|
||||
assert "disable 3" in script and "enable 3" in script
|
||||
assert "disable 5" in script and "enable 5" in script
|
||||
assert "disable 8" in script and "enable 8" in script
|
||||
assert "set *(" not in script
|
||||
print("pma_producer_trace selftest: PASS")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("pid", nargs="?", type=int)
|
||||
parser.add_argument("--output")
|
||||
parser.add_argument("--variant", choices=tuple(VARIANTS), default="mode0")
|
||||
parser.add_argument("--print-script", action="store_true")
|
||||
parser.add_argument("--selftest", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.selftest:
|
||||
selftest()
|
||||
return 0
|
||||
|
||||
pid = args.pid or transition.find_pid()
|
||||
if not pid:
|
||||
print("FIFA17.exe not found", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
fifa_base, fifa_path = advance.module_mapping(pid, advance.FIFA_MODULE)
|
||||
advance.validate_file(
|
||||
fifa_path,
|
||||
advance.PINNED_FIFA_SHA256,
|
||||
advance.FIFA_MODULE,
|
||||
)
|
||||
cards_base, cards_path = transition.cards_mapping(pid)
|
||||
transition.validate_cards(cards_path)
|
||||
output = args.output or f"/tmp/fifa17-pma-producer-{args.variant}-{pid}.log"
|
||||
script = build_script(pid, fifa_base, output, args.variant)
|
||||
environment = effective_environment(pid)
|
||||
print(
|
||||
"PMAPRODUCER PREPARED "
|
||||
f"pid={pid} variant={args.variant} fifa_base={fifa_base:#x} cards_base={cards_base:#x} "
|
||||
f"team_compat={environment.get('OPENFUT_FIFA17_SEASON_TEAM_COMPAT', '<absent>')} "
|
||||
f"pma_fix={environment.get('OPENFUT_FIFA17_OFFLINE_SEASONS_PMA_FIX', '<absent>')}"
|
||||
)
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
print(error, file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if args.print_script:
|
||||
print(script, end="")
|
||||
return 0
|
||||
if not shutil.which("gdb"):
|
||||
print("gdb not found", file=sys.stderr)
|
||||
return 2
|
||||
script_path = f"/tmp/fifa17-pma-producer-{args.variant}-{pid}.gdb"
|
||||
Path(script_path).write_text(script, encoding="utf-8")
|
||||
import os
|
||||
|
||||
os.execvp("gdb", ["gdb", "-q", "-nx", "-batch", "-x", script_path])
|
||||
return 127
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user