Refactor python shared snippets

This commit is contained in:
Théophile Bastian 2018-05-09 17:55:57 +02:00
parent 066f1f06c1
commit 789e79613c
4 changed files with 126 additions and 96 deletions

1
.gitignore vendored
View file

@ -32,3 +32,4 @@
*.app *.app
dwarf-assembly dwarf-assembly
__pycache__

View file

@ -9,7 +9,7 @@ import os
import subprocess import subprocess
from collections import namedtuple from collections import namedtuple
from generate_eh_elf import elf_so_deps from shared_python import elf_so_deps
''' An ELF object, including the path to the ELF itself, and the path to its ''' An ELF object, including the path to the ELF itself, and the path to its
@ -41,11 +41,11 @@ def section_size(elf_loc, section_name):
except subprocess.CalledProcessError as exn: except subprocess.CalledProcessError as exn:
raise Exception(("Cannot obtain section {} size for {}: objdump " raise Exception(("Cannot obtain section {} size for {}: objdump "
"terminated with exit code {}.").format( "terminated with exit code {}.").format(
section_name, elf_loc)) section_name, elf_loc, exn.returncode))
for line in objdump_out.split('\n'): for line in objdump_out.split('\n'):
line = line.strip() line = line.strip()
if not line or not ('0' <= line[0] <= '9'): # not a section line if not line or not '0' <= line[0] <= '9': # not a section line
continue continue
spl = line.split() spl = line.split()

View file

@ -9,10 +9,10 @@ all the shared objects it depends upon.
import os import os
import sys import sys
import subprocess import subprocess
import re
import tempfile import tempfile
import argparse import argparse
from collections import namedtuple
from shared_python import elf_so_deps, do_remote, is_newer
DWARF_ASSEMBLY_BIN = os.path.join( DWARF_ASSEMBLY_BIN = os.path.join(
@ -23,33 +23,6 @@ C_BIN = (
else os.environ['C']) else os.environ['C'])
def elf_so_deps(path):
''' Get the list of shared objects dependencies of the given ELF object.
This is obtained by running `ldd`. '''
deps_list = []
try:
ldd_output = subprocess.check_output(['/usr/bin/ldd', path]) \
.decode('utf-8')
ldd_re = re.compile(r'^.* => (.*) \(0x[0-9a-fA-F]*\)$')
ldd_lines = ldd_output.strip().split('\n')
for line in ldd_lines:
line = line.strip()
match = ldd_re.match(line)
if match is None:
continue # Just ignore that line — it might be eg. linux-vdso
deps_list.append(match.group(1))
return deps_list
except subprocess.CalledProcessError as e:
raise Exception(
("Cannot get dependencies for {}: ldd terminated with exit code "
"{}.").format(path, e.returncode))
def gen_dw_asm_c(obj_path, out_path, dwarf_assembly_args): def gen_dw_asm_c(obj_path, out_path, dwarf_assembly_args):
''' Generate the C code produced by dwarf-assembly from `obj_path`, saving ''' Generate the C code produced by dwarf-assembly from `obj_path`, saving
it as `out_path` ''' it as `out_path` '''
@ -61,66 +34,13 @@ def gen_dw_asm_c(obj_path, out_path, dwarf_assembly_args):
[DWARF_ASSEMBLY_BIN, obj_path] + dwarf_assembly_args) \ [DWARF_ASSEMBLY_BIN, obj_path] + dwarf_assembly_args) \
.decode('utf-8') .decode('utf-8')
out_handle.write(dw_asm_output) out_handle.write(dw_asm_output)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as exn:
raise Exception( raise Exception(
("Cannot generate C code from object file {} using {}: process " ("Cannot generate C code from object file {} using {}: process "
"terminated with exit code {}.").format( "terminated with exit code {}.").format(
obj_path, obj_path,
DWARF_ASSEMBLY_BIN, DWARF_ASSEMBLY_BIN,
e.returncode)) exn.returncode))
def do_remote(remote, command, send_files=None, retr_files=None):
def ssh_do(cmd_args, working_directory=None):
try:
cmd = ['ssh', remote]
if working_directory:
cmd += ['cd', working_directory, '&&']
cmd += cmd_args
return subprocess.check_output(cmd).decode('utf-8').strip()
except subprocess.CalledProcessError as e:
return None
def ssh_copy(what, where, is_upload):
if is_upload:
where = '{}:{}'.format(remote, where)
else:
what = '{}:{}'.format(remote, what)
subprocess.check_output(['scp', what, where])
TransferredFile = namedtuple('TransferredFile', 'local remote')
def interpret_transferred_file(descr):
if type(descr) == type(''):
return TransferredFile(descr, descr)
if os.path.isdir(descr[1]):
to = os.path.join(descr[1], descr[0])
else:
to = descr[1]
return TransferredFile(descr[0], to)
# Create temp dir
tmp_dir = ssh_do(['mktemp', '-d'])
# Upload `send_files`
for f in send_files:
dest = tmp_dir+'/'
ssh_copy(f, dest, is_upload=True)
# Do whatever must be done
output = ssh_do(command, working_directory=tmp_dir)
# Download `retr_files`
for f in map(interpret_transferred_file, retr_files):
src = os.path.join(tmp_dir, f.local)
ssh_copy(src, f.remote, is_upload=False)
# Remove temp dir
ssh_do(['rm', '-rf', tmp_dir])
return output
def gen_eh_elf(obj_path, args, dwarf_assembly_args=None): def gen_eh_elf(obj_path, args, dwarf_assembly_args=None):
@ -141,15 +61,8 @@ def gen_eh_elf(obj_path, args, dwarf_assembly_args=None):
out_base_name = os.path.basename(obj_path) + '.eh_elf' out_base_name = os.path.basename(obj_path) + '.eh_elf'
out_so_path = os.path.join(out_dir, (out_base_name + '.so')) out_so_path = os.path.join(out_dir, (out_base_name + '.so'))
try: if is_newer(out_so_path, obj_path):
so_mtime = os.path.getmtime(out_so_path) return # The object is recent enough, no need to recreate it
obj_mtime = os.path.getmtime(obj_path)
if obj_mtime < so_mtime:
return # The object is recent enough, no need to recreate it
except OSError:
pass
with tempfile.TemporaryDirectory() as compile_dir: with tempfile.TemporaryDirectory() as compile_dir:
# Generate the C source file # Generate the C source file

116
shared_python.py Normal file
View file

@ -0,0 +1,116 @@
""" Shared snippets between the various python scripts of this project """
import subprocess
import re
import os
from collections import namedtuple
def is_newer(file1, file2):
''' Returns True iff file1 is newer than file2 '''
try:
f1_mtime = os.path.getmtime(file1)
f2_mtime = os.path.getmtime(file2)
if f1_mtime > f2_mtime:
return True
except OSError:
pass
return False
def elf_so_deps(path):
''' Get the list of shared objects dependencies of the given ELF object.
This is obtained by running `ldd`. '''
deps_list = []
try:
ldd_output = subprocess.check_output(['/usr/bin/ldd', path]) \
.decode('utf-8')
ldd_re = re.compile(r'^.* => (.*) \(0x[0-9a-fA-F]*\)$')
ldd_lines = ldd_output.strip().split('\n')
for line in ldd_lines:
line = line.strip()
match = ldd_re.match(line)
if match is None:
continue # Just ignore that line — it might be eg. linux-vdso
deps_list.append(match.group(1))
return deps_list
except subprocess.CalledProcessError as exn:
raise Exception(
("Cannot get dependencies for {}: ldd terminated with exit code "
"{}.").format(path, exn.returncode))
def do_remote(remote, command, send_files=None, retr_files=None):
''' Execute remotely (via ssh) a given command
The command is executed on the machine described by `remote` (see ssh(1)).
send_files is a list of file paths that must be first copied at the root of
a temporary directory on `remote` before running the command. Consider
yourself jailed in that directory.
retr_files is a list of files that will be copied to the local machine
after the command is executed. Each list item can either be a string, which
is both the path on the remote and the local machine; or a pair
`(file_name, local_path)`. In the latter case, `file_name` is copied as
`local_path/file_name` if `local_path` is a directory, or as `local_path`
otherwise, on the local machine.
'''
def ssh_do(cmd_args, working_directory=None):
try:
cmd = ['ssh', remote]
if working_directory:
cmd += ['cd', working_directory, '&&']
cmd += cmd_args
return subprocess.check_output(cmd).decode('utf-8').strip()
except subprocess.CalledProcessError as e:
return None
def ssh_copy(what, where, is_upload):
if is_upload:
where = '{}:{}'.format(remote, where)
else:
what = '{}:{}'.format(remote, what)
subprocess.check_output(['scp', what, where])
TransferredFile = namedtuple('TransferredFile', 'local remote')
def interpret_transferred_file(descr):
if isinstance(descr, type('')):
return TransferredFile(descr, descr)
if os.path.isdir(descr[1]):
to = os.path.join(descr[1], descr[0])
else:
to = descr[1]
return TransferredFile(descr[0], to)
# Create temp dir
tmp_dir = ssh_do(['mktemp', '-d'])
# Upload `send_files`
for f in send_files:
dest = tmp_dir+'/'
ssh_copy(f, dest, is_upload=True)
# Do whatever must be done
output = ssh_do(command, working_directory=tmp_dir)
# Download `retr_files`
for f in map(interpret_transferred_file, retr_files):
src = os.path.join(tmp_dir, f.local)
ssh_copy(src, f.remote, is_upload=False)
# Remove temp dir
ssh_do(['rm', '-rf', tmp_dir])
return output