Skip to content

executable

Executable: management of the binary file in itself.

Executable

The executable class is used to interact with the binary file.

It handles access to the binary file itself (not the exported) for reads.

Note: The binary is read only once and stored in memory. This is done for performance purposes but does not cope well with low RAM systems and/or huge binaries.

Parameters:

Name Type Description Default
path Union[str, Path]

Path towards the executable file

required
endianness Endianness

How are stored the data

required

Attributes:

Name Type Description
exec_file Path

Path towards the executable file

endianness Endianness

Binary endianness

content bytes

Bytes of the binary

Raises:

Type Description
ValueError

If the file is not found

Source code in quokka/executable.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
class Executable:
    """The executable class is used to interact with the binary file.

    It handles access to the binary file itself (not the exported) for reads.

    Note: The binary is read only once and stored in memory. This is done for
    performance purposes but does not cope well with low RAM systems and/or huge
    binaries.

    Arguments:
        path: Path towards the executable file
        endianness: How are stored the data

    Attributes:
        exec_file: Path towards the executable file
        endianness: Binary endianness
        content: Bytes of the binary

    Raises:
        ValueError: If the file is not found
    """

    def __init__(self, path: Union[str, pathlib.Path], endianness: Endianness):
        """Constructor"""
        try:
            with open(path, "rb") as file:
                self.content: bytes = file.read()

        except FileNotFoundError:
            raise ValueError("File not found")

        self.exec_file: pathlib.Path = pathlib.Path(path)
        self.endianness: Endianness = endianness

    def read(self, offset: int, size: int) -> bytes:
        """Read `size` at `offset` in the file.

        This method should not be used directly and considered as part of a private API.
        The preferred method are read_bytes / read_string .

        Arguments:
            offset: File offset
            size: Read size

        Returns:
            The content that has been read

        Raises:
            ValueError: when the value is not in the file
        """
        try:
            return self.content[offset : offset + size]
        except IndexError as exc:
            raise ValueError(f"Content not found at offset {offset}") from exc

    def read_string(self, offset: int, size: Optional[int] = None) -> str:
        """Read a string in the file.

        If the size is not given, Quokka will try to read the string until the
        first null byte. That works only for null-terminated strings.

        If the string is null terminated, remove the trailing 0.

        Arguments:
            offset: String file offset
            size: String size if known.

        Returns:
            The decoded string

        Raises:
          ValueError: If the string is not found nor decoded.
        """

        if size is not None:
            try:
                string = self.read(offset, size).decode("utf-8")
            except UnicodeDecodeError as exc:
                raise ValueError("Unable to read or decode the string.") from exc

        else:
            try:
                null_byte = self.content.index(b"\x00", offset)
            except ValueError as exc:
                raise ValueError(
                    "String is not null-terminated and size was not given"
                ) from exc

            string = self.content[offset:null_byte].decode("utf-8")

        # FIX: When returning a single character string, it does not end with a '\0'
        if len(string) > 1 and string.endswith("\x00"):
            return string[:-1]

        return string

    def read_data(
        self, offset: int, data_type: DataType, size: Optional[int] = None
    ) -> Union[int, float, str]:
        """Read the data value.

        If the size is not specified, it is inferred from the data type.

        Arguments:
            offset: Data file offset
            data_type: Data type
            size: Read size

        Returns:
            The data value
        """

        # Read an int of size `read_size`
        def read_int(read_size: int) -> int:
            """Read an integer from the binary"""
            return int.from_bytes(self.read_bytes(offset, read_size), endianness)

        endianness: Literal["big", "little"]
        if self.endianness == Endianness.BIG_ENDIAN:
            endianness = "big"
            endianness_sign = ">"
        else:
            endianness = "little"
            endianness_sign = "<"

        if data_type == DataType.ASCII:
            if size is None:
                raise ValueError("No size specified when reading a DataType.ASCII")
            return self.read_string(offset, size)
        elif data_type == DataType.BYTE:
            return read_int(1 if size is None else size)
        elif data_type == DataType.WORD:
            return read_int(2 if size is None else size)
        elif data_type == DataType.DOUBLE_WORD:
            return read_int(4 if size is None else size)
        elif data_type == DataType.QUAD_WORD:
            return read_int(8 if size is None else size)
        elif data_type == DataType.OCTO_WORD:
            return read_int(16 if size is None else size)
        elif data_type == DataType.FLOAT:
            s = 4 if size is None else size
            return struct.unpack(f"{endianness_sign}f", self.read_bytes(offset, s))
        elif data_type == DataType.DOUBLE:
            s = 8 if size is None else size
            return struct.unpack(f"{endianness_sign}d", self.read_bytes(offset, s))
        else:
            raise NotImplementedError(
                f"Cannot read {data_type}. DataType not implemented."
            )

    def read_bytes(self, offset: int, size: int) -> bytes:
        """Read one (or more) byte(s) in the file at `offset`.

        This is mostly used to read instructions.

        Arguments:
            offset: File offset to read
            size: Number of bytes to read

        Returns:
            The bytes values
        """
        return self.read(offset, size)

__init__(path, endianness)

Constructor

Source code in quokka/executable.py
45
46
47
48
49
50
51
52
53
54
55
def __init__(self, path: Union[str, pathlib.Path], endianness: Endianness):
    """Constructor"""
    try:
        with open(path, "rb") as file:
            self.content: bytes = file.read()

    except FileNotFoundError:
        raise ValueError("File not found")

    self.exec_file: pathlib.Path = pathlib.Path(path)
    self.endianness: Endianness = endianness

read(offset, size)

Read size at offset in the file.

This method should not be used directly and considered as part of a private API. The preferred method are read_bytes / read_string .

Parameters:

Name Type Description Default
offset int

File offset

required
size int

Read size

required

Returns:

Type Description
bytes

The content that has been read

Raises:

Type Description
ValueError

when the value is not in the file

Source code in quokka/executable.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def read(self, offset: int, size: int) -> bytes:
    """Read `size` at `offset` in the file.

    This method should not be used directly and considered as part of a private API.
    The preferred method are read_bytes / read_string .

    Arguments:
        offset: File offset
        size: Read size

    Returns:
        The content that has been read

    Raises:
        ValueError: when the value is not in the file
    """
    try:
        return self.content[offset : offset + size]
    except IndexError as exc:
        raise ValueError(f"Content not found at offset {offset}") from exc

read_bytes(offset, size)

Read one (or more) byte(s) in the file at offset.

This is mostly used to read instructions.

Parameters:

Name Type Description Default
offset int

File offset to read

required
size int

Number of bytes to read

required

Returns:

Type Description
bytes

The bytes values

Source code in quokka/executable.py
173
174
175
176
177
178
179
180
181
182
183
184
185
def read_bytes(self, offset: int, size: int) -> bytes:
    """Read one (or more) byte(s) in the file at `offset`.

    This is mostly used to read instructions.

    Arguments:
        offset: File offset to read
        size: Number of bytes to read

    Returns:
        The bytes values
    """
    return self.read(offset, size)

read_data(offset, data_type, size=None)

Read the data value.

If the size is not specified, it is inferred from the data type.

Parameters:

Name Type Description Default
offset int

Data file offset

required
data_type DataType

Data type

required
size Optional[int]

Read size

None

Returns:

Type Description
Union[int, float, str]

The data value

Source code in quokka/executable.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def read_data(
    self, offset: int, data_type: DataType, size: Optional[int] = None
) -> Union[int, float, str]:
    """Read the data value.

    If the size is not specified, it is inferred from the data type.

    Arguments:
        offset: Data file offset
        data_type: Data type
        size: Read size

    Returns:
        The data value
    """

    # Read an int of size `read_size`
    def read_int(read_size: int) -> int:
        """Read an integer from the binary"""
        return int.from_bytes(self.read_bytes(offset, read_size), endianness)

    endianness: Literal["big", "little"]
    if self.endianness == Endianness.BIG_ENDIAN:
        endianness = "big"
        endianness_sign = ">"
    else:
        endianness = "little"
        endianness_sign = "<"

    if data_type == DataType.ASCII:
        if size is None:
            raise ValueError("No size specified when reading a DataType.ASCII")
        return self.read_string(offset, size)
    elif data_type == DataType.BYTE:
        return read_int(1 if size is None else size)
    elif data_type == DataType.WORD:
        return read_int(2 if size is None else size)
    elif data_type == DataType.DOUBLE_WORD:
        return read_int(4 if size is None else size)
    elif data_type == DataType.QUAD_WORD:
        return read_int(8 if size is None else size)
    elif data_type == DataType.OCTO_WORD:
        return read_int(16 if size is None else size)
    elif data_type == DataType.FLOAT:
        s = 4 if size is None else size
        return struct.unpack(f"{endianness_sign}f", self.read_bytes(offset, s))
    elif data_type == DataType.DOUBLE:
        s = 8 if size is None else size
        return struct.unpack(f"{endianness_sign}d", self.read_bytes(offset, s))
    else:
        raise NotImplementedError(
            f"Cannot read {data_type}. DataType not implemented."
        )

read_string(offset, size=None)

Read a string in the file.

If the size is not given, Quokka will try to read the string until the first null byte. That works only for null-terminated strings.

If the string is null terminated, remove the trailing 0.

Parameters:

Name Type Description Default
offset int

String file offset

required
size Optional[int]

String size if known.

None

Returns:

Type Description
str

The decoded string

Raises:

Type Description
ValueError

If the string is not found nor decoded.

Source code in quokka/executable.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def read_string(self, offset: int, size: Optional[int] = None) -> str:
    """Read a string in the file.

    If the size is not given, Quokka will try to read the string until the
    first null byte. That works only for null-terminated strings.

    If the string is null terminated, remove the trailing 0.

    Arguments:
        offset: String file offset
        size: String size if known.

    Returns:
        The decoded string

    Raises:
      ValueError: If the string is not found nor decoded.
    """

    if size is not None:
        try:
            string = self.read(offset, size).decode("utf-8")
        except UnicodeDecodeError as exc:
            raise ValueError("Unable to read or decode the string.") from exc

    else:
        try:
            null_byte = self.content.index(b"\x00", offset)
        except ValueError as exc:
            raise ValueError(
                "String is not null-terminated and size was not given"
            ) from exc

        string = self.content[offset:null_byte].decode("utf-8")

    # FIX: When returning a single character string, it does not end with a '\0'
    if len(string) > 1 and string.endswith("\x00"):
        return string[:-1]

    return string