2017-02-26 1 views
3

On m'a dit de corriger un bogue dans une application héritée.Soulever Exception sur un appel système indésirable

Je peux reproduire un bogue, mais je n'ai aucune idée de la ligne de code source python à laquelle l'erreur est exécutée.

Je peux voir l'échec pertinent avec strace: Un fichier est ouvert, qui ne devrait pas être ouvert.

Je voudrais que l'instruction open() linux-syscall génère une exception dans l'interpréteur python. Mon objectif: je veux voir la pile de paquets pour pouvoir corriger le bug. De cette façon, j'ai pu éviter le temps passé à traverser beaucoup de lignes avec un débogueur.

La même chose avec d'autres mots: si le syscall est exécuté, ce qui entraîne la sortie de strace de open("/somefile", O_RDONLY) = 4 l'interpréteur python devrait sortir avec un retraçage.

Quelqu'un at-il une solution?

S'il vous plaît laissez un commentaire si vous ne comprenez pas ce que je cherche.

+0

Serait-il possible de renommer temporairement '/ somefile' afin que tout tentative d'appel échoue pour l'ouvrir? (En supposant que le code ne gère pas déjà la situation du fichier manquant lui-même.) – chepner

+0

Non, il ne sera pas utile de renommer le fichier. – guettli

Répondre

3

Vous pouvez exécuter python sous gdb, définissez un (conditionnel) point d'arrêt sur la open() syscall (ou plutôt la fonction de talon dans la libc à travers lequel il est appelé), et, lorsque le point d'arrêt est frappé, envoyer un SIGINT signaler au processus python et le laisser continuer, après quoi l'exécution du script python devrait être interrompue avec la trace de la pile désirée.

Le script shell ci-dessous automatise cette procédure.

Utilisation:

stack_trace_on_openfilename-- pythonscript.py[script args]


stack_trace_on_open:

#!/usr/bin/env bash 

myname="$(basename "$0")" 

if [[ $# -lt 4 || "$2" != '--' ]] 
then 
    echo >&2 "Usage: $myname <filename> -- python <script.py> [script args ...]" 
    exit 1 
fi 

fname=$1 
python_exe="$3" 
shift 3 

gdb -q "$python_exe" <<END 
set breakpoint pending on 
break open 
condition 1 strcmp(\$rdi,"$fname") == 0 
run "[email protected]" 
signal 2 
cont 
quit 
END 

Démonstration:

$ cat test.py 
import ctypes 

clib = ctypes.CDLL(None) 
fd = clib.open("/dev/urandom", 0) 
clib.close(fd) 

$ ./stack_trace_on_open /dev/urandom -- python test.py 
Reading symbols from python...(no debugging symbols found)...done. 
(gdb) (gdb) Function "open" not defined. 
Breakpoint 1 (open) pending. 
(gdb) (gdb) Starting program: /usr/bin/python "test.py" 
[Thread debugging using libthread_db enabled] 
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 

Breakpoint 1, open64() at ../sysdeps/unix/syscall-template.S:84 
84 ../sysdeps/unix/syscall-template.S: No such file or directory. 
(gdb) Continuing with signal SIGINT. 

Breakpoint 1, open64() at ../sysdeps/unix/syscall-template.S:84 
84 in ../sysdeps/unix/syscall-template.S 
(gdb) Continuing. 
Traceback (most recent call last):    #  <-------- 
    File "test.py", line 4, in <module>    #  <-------- 
    fd = clib.open("/dev/urandom", 0)    #  <-------- 
KeyboardInterrupt 
[Inferior 1 (process 14248) exited with code 01] 
(gdb) 
+0

Wow, fonctionne très bien. Jusqu'à présent, je ne suis pas encore expert en gdb. Je pense qu'il y a des choses magiques possibles avec ça. Vous obtenez la prime. – guettli

3

Nous pouvons faire un patch sur open avant les modules d'importation, voici un exemple:

dans test.py:

def func(): 
    with open('test', 'w') as f: 
     pass 

dans test2.py:

try: 
    import __builtin__ # for python2 
except ImportError: 
    import builtins as __builtin__ #for python3 

import copy 
import traceback 

orig_open = copy.copy(__builtin__.open) 

def myopen(*args): 
    traceback.print_stack() 
    return orig_open(*args) 

__builtin__.open = myopen 

from test import funC# Note that we import the module after patching on open() 

func() 

et quand func() est appelé à test2.py, la pile d'appel sera imprimée:

$ python test2.py 
    File "test2.py", line 19, in <module> 
    func() 
    File "/tmp/test.py", line 4, in func 
    with open('test', 'w') as f: 
    File "test2.py", line 12, in myopen 
    traceback.print_stack() 
+0

J'ai essayé, mais cela n'aide pas. Je suppose que le sys-appel ouvert se passe dans une ancienne extension c. – guettli

+0

@guettli Si j'étais vous, je téléchargerais le code source et utiliser 'grep -R' ou' ag' pour rechercher le nom de fichier du fichier ouvert parmi toutes les sources. Puisque l'opération d'ouverture peut se produire dans une bibliothèque binaire, si la bibliothèque n'est pas conforme aux informations de débogage, il sera difficile de trouver l'emplacement spécifique où 'open()' est appelé même si nous pouvons imprimer la pile d'appels. – zsrkmyn

+0

depuis que j'ai eu l'idée "Stacktrace on syscall", j'ai déjà trouvé plusieurs cas d'utilisation où cet outil de débogage de base serait utile. Oui, pour le cas d'utilisation de cette question, je pourrais trouver une solution de rechange. – guettli