|  | 
|  | 1 | +# Copyright (c) 2025, Oracle and/or its affiliates. | 
|  | 2 | +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ | 
|  | 3 | +import argparse | 
|  | 4 | +from typing import List | 
|  | 5 | +from typing import Tuple | 
|  | 6 | + | 
|  | 7 | +from drgn import Object | 
|  | 8 | +from drgn import Program | 
|  | 9 | + | 
|  | 10 | +from drgn_tools.corelens import CorelensModule | 
|  | 11 | + | 
|  | 12 | +PAGE_SIZE = 4096 | 
|  | 13 | + | 
|  | 14 | + | 
|  | 15 | +def init_ring_buffer_pages(prog: Program, cpu_buffer: Object) -> List[Object]: | 
|  | 16 | +    """ | 
|  | 17 | +    Construct a linear page list from a cpu_buffer | 
|  | 18 | +
 | 
|  | 19 | +    :param cpu_buffer: ``struct ring_buffer_per_cpu `` | 
|  | 20 | +    :return: List of ``struct buffer_page`` objects. | 
|  | 21 | +    """ | 
|  | 22 | +    global PAGE_SIZE | 
|  | 23 | +    PAGE_SIZE = prog["PAGE_SIZE"].value_() | 
|  | 24 | + | 
|  | 25 | +    pages_list = [] | 
|  | 26 | +    head_page_addr = cpu_buffer.head_page.value_() | 
|  | 27 | +    if head_page_addr == 0: | 
|  | 28 | +        return [] | 
|  | 29 | +    pages_list.append(head_page_addr) | 
|  | 30 | +    real_head_page_addr = cpu_buffer.head_page.value_() | 
|  | 31 | +    curr_addr = head_page_addr | 
|  | 32 | +    count = 0 | 
|  | 33 | +    while True: | 
|  | 34 | +        if count >= cpu_buffer.nr_pages: | 
|  | 35 | +            break | 
|  | 36 | +        buffer_page_obj = Object(prog, "struct buffer_page", address=curr_addr) | 
|  | 37 | +        next_ptr = buffer_page_obj.list.next.value_() | 
|  | 38 | +        if next_ptr == 0: | 
|  | 39 | +            break | 
|  | 40 | +        # Check for flag bits | 
|  | 41 | +        if next_ptr & 0x3: | 
|  | 42 | +            unflagged = next_ptr & ~0x3 | 
|  | 43 | +            real_head_page_addr = unflagged | 
|  | 44 | +            next_ptr = unflagged | 
|  | 45 | +        if next_ptr == head_page_addr: | 
|  | 46 | +            break | 
|  | 47 | +        pages_list.append(next_ptr) | 
|  | 48 | +        curr_addr = next_ptr | 
|  | 49 | +        count += 1 | 
|  | 50 | + | 
|  | 51 | +    # Find head_index | 
|  | 52 | +    head_index = 0 | 
|  | 53 | +    for idx, page_addr in enumerate(pages_list): | 
|  | 54 | +        if page_addr == real_head_page_addr: | 
|  | 55 | +            head_index = idx | 
|  | 56 | +            break | 
|  | 57 | + | 
|  | 58 | +    linear_pages = [] | 
|  | 59 | +    reader_page_addr = cpu_buffer.reader_page.value_() | 
|  | 60 | +    commit_page_addr = cpu_buffer.commit_page.value_() | 
|  | 61 | + | 
|  | 62 | +    if reader_page_addr: | 
|  | 63 | +        linear_pages.append(reader_page_addr) | 
|  | 64 | + | 
|  | 65 | +    if reader_page_addr != commit_page_addr: | 
|  | 66 | +        n = len(pages_list) | 
|  | 67 | +        i = head_index | 
|  | 68 | +        while True: | 
|  | 69 | +            page_addr = pages_list[i] | 
|  | 70 | +            if page_addr != reader_page_addr: | 
|  | 71 | +                linear_pages.append(page_addr) | 
|  | 72 | +            if page_addr == commit_page_addr: | 
|  | 73 | +                break | 
|  | 74 | +            i = (i + 1) % n | 
|  | 75 | +            if i == head_index: | 
|  | 76 | +                break | 
|  | 77 | + | 
|  | 78 | +    return linear_pages | 
|  | 79 | + | 
|  | 80 | + | 
|  | 81 | +def collect_cpu_data_blocks( | 
|  | 82 | +    prog: Program, linear_pages_all_cpus: List[List[Object]] | 
|  | 83 | +) -> Tuple[List[bytes], List[int]]: | 
|  | 84 | +    """ | 
|  | 85 | +    Reads buffer_pages and converts them to bytes. | 
|  | 86 | +
 | 
|  | 87 | +    :param linear_pages_all_cpus:  Lists of ``struct buffer_page`` | 
|  | 88 | +    :return: A tuple of data_blocks list in bytes and their sizes | 
|  | 89 | +    """ | 
|  | 90 | +    cpu_data_blocks = [] | 
|  | 91 | +    cpu_data_sizes = [] | 
|  | 92 | +    for cpu_linear_pages in linear_pages_all_cpus: | 
|  | 93 | +        data_bytes = bytearray() | 
|  | 94 | +        for page_addr in cpu_linear_pages: | 
|  | 95 | +            try: | 
|  | 96 | +                bp = Object(prog, "struct buffer_page", address=page_addr) | 
|  | 97 | +                raw_page_addr = bp.page.value_() | 
|  | 98 | +                if raw_page_addr == 0: | 
|  | 99 | +                    continue | 
|  | 100 | +                page_bytes = prog.read(raw_page_addr, PAGE_SIZE) | 
|  | 101 | +                data_bytes += page_bytes | 
|  | 102 | +            except Exception as e: | 
|  | 103 | +                print( | 
|  | 104 | +                    f"Exception collecting data for CPU page {hex(page_addr)}: {e}" | 
|  | 105 | +                ) | 
|  | 106 | +                continue | 
|  | 107 | +        cpu_data_blocks.append(bytes(data_bytes)) | 
|  | 108 | +        cpu_data_sizes.append(len(data_bytes)) | 
|  | 109 | + | 
|  | 110 | +    return cpu_data_blocks, cpu_data_sizes | 
|  | 111 | + | 
|  | 112 | + | 
|  | 113 | +def dump_trace_dat( | 
|  | 114 | +    prog: Program, | 
|  | 115 | +    linear_pages_all_cpus: List[List[Object]], | 
|  | 116 | +    output_path: str = "./trace.dat", | 
|  | 117 | +): | 
|  | 118 | +    """ | 
|  | 119 | +    Formats and dumps ftrace buffer as trace.dat parsable by trace-cmd | 
|  | 120 | +
 | 
|  | 121 | +    :param linear_pages_all_cpus:  Lists of ``struct buffer_page`` | 
|  | 122 | +    :return: A tuple of data_blocks list in bytes and their sizes | 
|  | 123 | +    """ | 
|  | 124 | +    is_little_endian = True | 
|  | 125 | +    num_cpus = len(linear_pages_all_cpus) | 
|  | 126 | +    data_offset = 16 | 
|  | 127 | +    data_size = PAGE_SIZE - data_offset | 
|  | 128 | +    header_page_text = ( | 
|  | 129 | +        "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0;\n" | 
|  | 130 | +        f"\tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1;\n" | 
|  | 131 | +        f"\tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1;\n" | 
|  | 132 | +        f"\tfield: char data;\toffset:{data_offset};\tsize:{data_size};\tsigned:0;\n" | 
|  | 133 | +    ) | 
|  | 134 | +    header_event_text = ( | 
|  | 135 | +        "\tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n" | 
|  | 136 | +        "\tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n" | 
|  | 137 | +        "\tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n" | 
|  | 138 | +        "\tfield:int common_pid;\t\toffset:4;\tsize:4;\tsigned:1;\n" | 
|  | 139 | +        "\tfield:int common_padding;\t\toffset:8;\tsize:4;\tsigned:1;\n" | 
|  | 140 | +    ) | 
|  | 141 | +    header_page_bytes = header_page_text.encode("ascii") | 
|  | 142 | +    header_event_bytes = header_event_text.encode("ascii") | 
|  | 143 | + | 
|  | 144 | +    cpu_data_blocks, cpu_data_sizes = collect_cpu_data_blocks( | 
|  | 145 | +        prog, linear_pages_all_cpus | 
|  | 146 | +    ) | 
|  | 147 | + | 
|  | 148 | +    with open(output_path, "wb") as f: | 
|  | 149 | +        # magic header | 
|  | 150 | +        magic = b"Dtracing6\0\0\0" | 
|  | 151 | +        magic_tag = 0x17 if is_little_endian else 0x18 | 
|  | 152 | +        f.write(magic_tag.to_bytes(1, "little")) | 
|  | 153 | +        f.write(0x08.to_bytes(1, "little")) | 
|  | 154 | +        f.write(magic) | 
|  | 155 | +        f.write(PAGE_SIZE.to_bytes(4, "little" if is_little_endian else "big")) | 
|  | 156 | + | 
|  | 157 | +        # Write header_page section | 
|  | 158 | +        f.write(b"header_page\0") | 
|  | 159 | +        f.write( | 
|  | 160 | +            len(header_page_bytes).to_bytes( | 
|  | 161 | +                8, "little" if is_little_endian else "big" | 
|  | 162 | +            ) | 
|  | 163 | +        ) | 
|  | 164 | +        f.write(header_page_bytes) | 
|  | 165 | + | 
|  | 166 | +        # Write header_event section | 
|  | 167 | +        f.write(b"header_event\0") | 
|  | 168 | +        f.write( | 
|  | 169 | +            len(header_event_bytes).to_bytes( | 
|  | 170 | +                8, "little" if is_little_endian else "big" | 
|  | 171 | +            ) | 
|  | 172 | +        ) | 
|  | 173 | +        f.write(header_event_bytes) | 
|  | 174 | + | 
|  | 175 | +        f.write( | 
|  | 176 | +            (0).to_bytes(4, "little" if is_little_endian else "big") | 
|  | 177 | +        )  # event format | 
|  | 178 | +        f.write( | 
|  | 179 | +            (0).to_bytes(4, "little" if is_little_endian else "big") | 
|  | 180 | +        )  # event systems | 
|  | 181 | +        f.write( | 
|  | 182 | +            (0).to_bytes(4, "little" if is_little_endian else "big") | 
|  | 183 | +        )  # kallsyms | 
|  | 184 | +        f.write( | 
|  | 185 | +            (0).to_bytes(4, "little" if is_little_endian else "big") | 
|  | 186 | +        )  # ftrace_printk | 
|  | 187 | +        f.write( | 
|  | 188 | +            (0).to_bytes(8, "little" if is_little_endian else "big") | 
|  | 189 | +        )  # cmdlines | 
|  | 190 | + | 
|  | 191 | +        f.write(num_cpus.to_bytes(4, "little" if is_little_endian else "big")) | 
|  | 192 | +        mode_str = b"flyrecord\0".ljust(10, b"\0")[:10] | 
|  | 193 | +        f.write(mode_str) | 
|  | 194 | + | 
|  | 195 | +        table_offset = f.tell() | 
|  | 196 | +        f.write(b"\x00" * (num_cpus * 16)) | 
|  | 197 | + | 
|  | 198 | +        # Align | 
|  | 199 | +        align = (-f.tell()) % PAGE_SIZE | 
|  | 200 | +        if align: | 
|  | 201 | +            f.write(b"\x00" * align) | 
|  | 202 | +        data_start_offset = f.tell() | 
|  | 203 | + | 
|  | 204 | +        # Write data and record size | 
|  | 205 | +        cpu_offsets, cpu_data_sizes = [], [] | 
|  | 206 | +        current_offset = data_start_offset | 
|  | 207 | +        for data in cpu_data_blocks: | 
|  | 208 | +            cpu_offsets.append(current_offset) | 
|  | 209 | +            if data: | 
|  | 210 | +                f.write(data) | 
|  | 211 | +                cpu_data_sizes.append(len(data)) | 
|  | 212 | +                current_offset += len(data) | 
|  | 213 | +            else: | 
|  | 214 | +                cpu_data_sizes.append(0) | 
|  | 215 | + | 
|  | 216 | +        # Seek back and write table | 
|  | 217 | +        f.seek(table_offset) | 
|  | 218 | +        for off, sz in zip(cpu_offsets, cpu_data_sizes): | 
|  | 219 | +            f.write(off.to_bytes(8, "little")) | 
|  | 220 | +            f.write(sz.to_bytes(8, "little")) | 
|  | 221 | + | 
|  | 222 | +    print( | 
|  | 223 | +        f"trace.dat dumped with {num_cpus} CPUs and {sum(cpu_data_sizes)} total bytes of data." | 
|  | 224 | +    ) | 
|  | 225 | + | 
|  | 226 | + | 
|  | 227 | +def dump_ftrace(prog: Program) -> None: | 
|  | 228 | +    """Dumps ftrace buffer.""" | 
|  | 229 | +    try: | 
|  | 230 | +        trace_array = prog["global_trace"] | 
|  | 231 | +    except KeyError: | 
|  | 232 | +        print("Error: global_trace symbol not found.") | 
|  | 233 | +        return | 
|  | 234 | +    try: | 
|  | 235 | +        trace_buf = trace_array.array_buffer.buffer | 
|  | 236 | +    except AttributeError: | 
|  | 237 | +        print("Error: Only UEK7 and above is supported.") | 
|  | 238 | +        return | 
|  | 239 | +    if trace_buf.value_() == 0: | 
|  | 240 | +        print("No ftrace trace buffer found") | 
|  | 241 | +        return | 
|  | 242 | + | 
|  | 243 | +    cpu_count = int(trace_buf.cpus) | 
|  | 244 | +    linear_pages_all_cpus = [] | 
|  | 245 | +    for cpu in range(cpu_count): | 
|  | 246 | +        cpu_buf = trace_buf.buffers[cpu] | 
|  | 247 | +        linear_pages = init_ring_buffer_pages(prog, cpu_buf) | 
|  | 248 | +        linear_pages_all_cpus.append(linear_pages) | 
|  | 249 | +    dump_trace_dat(prog, linear_pages_all_cpus) | 
|  | 250 | + | 
|  | 251 | + | 
|  | 252 | +class Ftrace(CorelensModule): | 
|  | 253 | +    """ | 
|  | 254 | +    Dumps ftrace buffer | 
|  | 255 | +    """ | 
|  | 256 | + | 
|  | 257 | +    name = "ftrace" | 
|  | 258 | + | 
|  | 259 | +    def run(self, prog: Program, args: argparse.Namespace) -> None: | 
|  | 260 | +        dump_ftrace(prog) | 
0 commit comments