├── debian ├── source │ └── format ├── lintian-overrides ├── libudebug.install ├── udebugd.install ├── libudebug-dev.install ├── generate-changelog.sh ├── rules ├── copyright └── control ├── .gitignore ├── examples ├── test-source.uc └── test-source.c ├── udebug.h ├── udebug-pcap.h ├── CMakeLists.txt ├── main.c ├── server.h ├── ring.c ├── README.md ├── lib.c ├── lib-pcap.c ├── ubus.c ├── client.c ├── udebug-cli └── lib-ucode.c /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/lintian-overrides: -------------------------------------------------------------------------------- 1 | no-manual-page 2 | -------------------------------------------------------------------------------- /debian/libudebug.install: -------------------------------------------------------------------------------- 1 | usr/lib/libudebug.so.* 2 | -------------------------------------------------------------------------------- /debian/udebugd.install: -------------------------------------------------------------------------------- 1 | usr/sbin/udebugd 2 | usr/sbin/udebug 3 | usr/lib/ucode/udebug.so 4 | -------------------------------------------------------------------------------- /debian/libudebug-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/udebug.h 2 | usr/include/udebug-pcap.h 3 | usr/lib/libudebug.so 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | CMakeCache.txt 3 | CMakeFiles 4 | .* 5 | *.cmake 6 | *.so 7 | *.dylib 8 | *.pcap 9 | udebugd 10 | test-source 11 | test-receiver 12 | test-pcap 13 | install_manifest.txt 14 | obj-* 15 | debian/.debhelper/ 16 | debian/debhelper-build-stamp 17 | debian/*.debhelper.log 18 | debian/*.substvars 19 | debian/files 20 | debian/libudebug/ 21 | debian/libudebug-dev/ 22 | debian/udebugd/ 23 | debian/changelog 24 | debian/tmp/ 25 | -------------------------------------------------------------------------------- /examples/test-source.uc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ucode 2 | let udebug = require("udebug"); 3 | 4 | udebug.init("./udebug.sock"); 5 | let buf = udebug.create_ring({ 6 | name: "counter", 7 | size: 256, 8 | entries: 128, 9 | }); 10 | 11 | if (!buf) { 12 | warn(`Failed to create buffer\n`); 13 | exit(1); 14 | } 15 | 16 | let count = 0; 17 | signal('SIGINT', () => exit(0)); 18 | signal('SIGTERM', () => exit(0)); 19 | while (true) { 20 | buf.add(`count=${count}`); 21 | if (count++ > 1000) 22 | sleep(1000); 23 | } 24 | -------------------------------------------------------------------------------- /debian/generate-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")/.." 4 | 5 | COMMIT_DATE=$(git log -1 --format='%cd' --date=format:'%Y%m%d' 2>/dev/null || echo '00000000') 6 | COMMIT_HASH=$(git log -1 --format='%h' 2>/dev/null || echo 'unknown') 7 | COMMIT_TIMESTAMP=$(git log -1 --format='%cd' --date=rfc2822 2>/dev/null || date -R) 8 | 9 | cat > debian/changelog < ${COMMIT_TIMESTAMP} 15 | EOF 16 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all 4 | 5 | BUILD_DIR = obj-$(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 6 | 7 | %: 8 | dh $@ --builddirectory=$(BUILD_DIR) 9 | 10 | override_dh_auto_clean: 11 | dh_auto_clean --builddirectory=$(BUILD_DIR) 12 | rm -rf $(BUILD_DIR) 13 | 14 | override_dh_auto_configure: 15 | cmake -S . -B $(BUILD_DIR) \ 16 | -DCMAKE_INSTALL_PREFIX=/usr \ 17 | -DCMAKE_BUILD_TYPE=None \ 18 | -DCMAKE_VERBOSE_MAKEFILE=ON \ 19 | -DABIVERSION=$(shell dpkg-parsechangelog | sed -rne 's/^Version: 0\.0\.([0-9]+).*$$/\1/p') \ 20 | -DRUNSTATEDIR=/run 21 | -------------------------------------------------------------------------------- /examples/test-source.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "udebug.h" 4 | 5 | static struct udebug ud; 6 | static struct udebug_buf udb; 7 | 8 | struct udebug_buf_flag buf_flags[] = { 9 | { "enabled", 1ULL } 10 | }; 11 | static const struct udebug_buf_meta buf_meta = { 12 | .name = "counter", 13 | .format = UDEBUG_FORMAT_STRING, 14 | .flags = buf_flags, 15 | .n_flags = ARRAY_SIZE(buf_flags), 16 | }; 17 | 18 | int main(int argc, char **argv) 19 | { 20 | int count = 0; 21 | 22 | udebug_init(&ud); 23 | udebug_connect(&ud, "./udebug.sock"); 24 | 25 | udebug_buf_init(&udb, 256, 128); 26 | udebug_buf_add(&ud, &udb, &buf_meta); 27 | while (1) { 28 | udebug_entry_init(&udb); 29 | udebug_entry_printf(&udb, "count=%d", count++); 30 | udebug_entry_add(&udb); 31 | if (count > 10000) 32 | sleep(1); 33 | } 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: udebug 3 | Source: https://git.openwrt.org/project/udebug.git 4 | 5 | Files: * 6 | Copyright: 2024- Felix Fietkau 7 | License: ISC 8 | 9 | Permission to use, copy, modify, and/or distribute this software for any 10 | purpose with or without fee is hereby granted, provided that the above 11 | copyright notice and this permission notice appear in all copies. 12 | . 13 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | -------------------------------------------------------------------------------- /udebug.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct udebug_ubus; 5 | typedef void (*udebug_config_cb)(struct udebug_ubus *ctx, struct blob_attr *data, bool enabled); 6 | 7 | struct udebug_ubus_ring { 8 | struct udebug_buf *buf; 9 | const struct udebug_buf_meta *meta; 10 | unsigned int size, default_size; 11 | unsigned int entries, default_entries; 12 | }; 13 | 14 | struct udebug_ubus { 15 | struct ubus_context *ubus; 16 | struct uloop_timeout t; 17 | const char *service; 18 | struct ubus_subscriber sub; 19 | udebug_config_cb cb; 20 | }; 21 | 22 | void udebug_netlink_msg(struct udebug_buf *buf, uint16_t proto, const void *data, size_t len); 23 | 24 | void udebug_ubus_init(struct udebug_ubus *ctx, struct ubus_context *ubus, 25 | const char *service, udebug_config_cb cb); 26 | void udebug_ubus_ring_init(struct udebug *ud, struct udebug_ubus_ring *ring); 27 | void udebug_ubus_apply_config(struct udebug *ud, struct udebug_ubus_ring *rings, int n, 28 | struct blob_attr *data, bool enabled); 29 | void udebug_ubus_free(struct udebug_ubus *ctx); 30 | -------------------------------------------------------------------------------- /udebug-pcap.h: -------------------------------------------------------------------------------- 1 | #ifndef __UDEBUG_PCAP_H 2 | #define __UDEBUG_PCAP_H 3 | 4 | #include 5 | #include 6 | 7 | struct pcap_context { 8 | uint32_t iface_id; 9 | void *buf; 10 | }; 11 | 12 | struct pcap_meta { 13 | const char *hw, *os, *app; 14 | }; 15 | 16 | struct pcap_interface_meta { 17 | const char *name; 18 | const char *description; 19 | uint8_t time_res; 20 | uint16_t link_type; 21 | }; 22 | 23 | struct pcap_dbus_meta { 24 | const char *path, *interface, *name; 25 | const char *src, *dest; 26 | }; 27 | 28 | int pcap_init(struct pcap_context *p, struct pcap_meta *meta); 29 | int pcap_interface_init(struct pcap_context *p, uint32_t *id, 30 | struct pcap_interface_meta *meta); 31 | static inline bool 32 | pcap_interface_is_valid(struct pcap_context *p, uint32_t idx) 33 | { 34 | return idx <= p->iface_id; 35 | } 36 | 37 | void pcap_packet_init(uint32_t iface, uint64_t timestamp); 38 | void pcap_dbus_init_string(const struct udebug_packet_info *meta, const char *val); 39 | void pcap_dbus_init_blob(const struct udebug_packet_info *meta, struct blob_attr *val, bool dict); 40 | void *pcap_packet_append(const void *data, size_t len); 41 | void pcap_packet_done(void); 42 | 43 | int pcap_interface_rbuf_init(struct pcap_context *p, struct udebug_remote_buf *rb); 44 | int pcap_snapshot_packet_init(struct udebug *ctx, struct udebug_iter *it); 45 | 46 | bool pcap_block_write_file(FILE *f); 47 | void *pcap_block_get(size_t *len); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | PROJECT(udebugd C) 4 | 5 | ADD_DEFINITIONS(-O2 -Wall -fwrapv -Werror --std=gnu99 -g3 -Wmissing-declarations -DRUNSTATEDIR="${RUNSTATEDIR}") 6 | FIND_LIBRARY(ubus NAMES ubus) 7 | FIND_LIBRARY(ubox NAMES ubox) 8 | 9 | FIND_PATH(ubus_include_dir NAMES libubus.h) 10 | FIND_PATH(uloop_include_dir NAMES libubox/uloop.h) 11 | FIND_PATH(ucode_include_dir NAMES ucode/module.h) 12 | INCLUDE_DIRECTORIES(${uloop_include_dir} ${ubus_include_dir} ${ucode_include_dir}) 13 | 14 | IF(APPLE) 15 | SET(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") 16 | ENDIF() 17 | 18 | ADD_LIBRARY(udebug SHARED lib.c lib-pcap.c) 19 | IF(ABIVERSION) 20 | SET_TARGET_PROPERTIES(udebug PROPERTIES VERSION ${ABIVERSION}) 21 | ENDIF() 22 | TARGET_LINK_LIBRARIES(udebug ${ubox} ${ubus}) 23 | 24 | ADD_EXECUTABLE(udebugd main.c client.c ring.c ubus.c) 25 | TARGET_LINK_LIBRARIES(udebugd udebug ${ubox} ${ubus}) 26 | 27 | ADD_LIBRARY(ucode_lib MODULE lib-ucode.c) 28 | SET_TARGET_PROPERTIES(ucode_lib PROPERTIES OUTPUT_NAME udebug PREFIX "") 29 | TARGET_LINK_OPTIONS(ucode_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) 30 | TARGET_LINK_LIBRARIES(ucode_lib ${ubox} udebug) 31 | 32 | INSTALL(FILES udebug.h udebug-pcap.h 33 | DESTINATION include 34 | ) 35 | INSTALL(FILES udebug-cli 36 | DESTINATION sbin 37 | RENAME udebug 38 | ) 39 | INSTALL(TARGETS udebugd udebug 40 | RUNTIME DESTINATION sbin 41 | LIBRARY DESTINATION lib 42 | ) 43 | INSTALL(TARGETS ucode_lib 44 | LIBRARY DESTINATION lib/ucode 45 | ) 46 | 47 | ADD_CUSTOM_TARGET(debian 48 | COMMAND ${CMAKE_COMMAND} -E echo "Generating debian/changelog from git..." 49 | COMMAND ${CMAKE_SOURCE_DIR}/debian/generate-changelog.sh 50 | COMMAND dpkg-buildpackage -b -uc -us 51 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 52 | COMMENT "Building Debian package" 53 | ) 54 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: udebug 2 | Maintainer: Felix Fietkau 3 | Section: libs 4 | Priority: optional 5 | Standards-Version: 4.5.0 6 | Rules-Requires-Root: no 7 | Build-Depends: debhelper-compat (= 12), cmake, pkgconf, libubox-dev 8 | Homepage: https://git.openwrt.org/project/udebug.git 9 | Vcs-Browser: https://git.openwrt.org/project/udebug.git 10 | Vcs-Git: https://git.openwrt.org/project/udebug.git 11 | 12 | Package: libudebug 13 | Architecture: any 14 | Multi-Arch: same 15 | Pre-Depends: ${misc:Pre-Depends} 16 | Depends: ${shlibs:Depends}, ${misc:Depends} 17 | Section: libs 18 | Description: OpenWrt debug ring buffer library 19 | Libudebug is a debug ring buffer library used by OpenWrt. It provides 20 | facilities for debug message collection, filtering, and export to pcap 21 | format for analysis. 22 | . 23 | This package contains the shared library libudebug. 24 | 25 | Package: libudebug-dev 26 | Architecture: any 27 | Multi-Arch: same 28 | Depends: libudebug (= ${binary:Version}), libc6-dev|libc-dev, libubox-dev, ${misc:Depends} 29 | Section: libdevel 30 | Description: Development files for libudebug 31 | Libudebug is a debug ring buffer library used by OpenWrt. It provides 32 | facilities for debug message collection, filtering, and export to pcap 33 | format for analysis. 34 | . 35 | This package contains the development files (headers and symlinks) 36 | for libudebug. 37 | 38 | Package: udebugd 39 | Architecture: any 40 | Multi-Arch: foreign 41 | Depends: libudebug (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} 42 | Description: OpenWrt debug ring buffer daemon and utilities 43 | Libudebug is a debug ring buffer library used by OpenWrt. It provides 44 | facilities for debug message collection, filtering, and export to pcap 45 | format for analysis. 46 | . 47 | This package contains the udebugd daemon, udebug-cli utility, and the 48 | ucode module for accessing debug ring buffers. 49 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "server.h" 12 | 13 | static struct uloop_fd server_fd; 14 | static char *socket_name; 15 | int debug_level = 3; 16 | 17 | static void server_fd_cb(struct uloop_fd *ufd, unsigned int events) 18 | { 19 | D(3, "cb"); 20 | while (1) { 21 | int fd = accept(ufd->fd, NULL, 0); 22 | if (fd < 0) { 23 | if (errno == EINTR || errno == ECONNABORTED) 24 | continue; 25 | return; 26 | } 27 | 28 | client_alloc(fd); 29 | } 30 | } 31 | 32 | static int usage(const char *progname) 33 | { 34 | fprintf(stderr, "Usage: %s [options]\n" 35 | "Options:\n" 36 | " -s : Set path to socket\n" 37 | "\n", progname); 38 | return 1; 39 | } 40 | 41 | static void mkdir_sockdir(void) 42 | { 43 | char *sep; 44 | 45 | sep = strrchr(socket_name, '/'); 46 | if (!sep) 47 | return; 48 | 49 | *sep = 0; 50 | mkdir(socket_name, 0755); 51 | *sep = '/'; 52 | } 53 | 54 | int main(int argc, char **argv) 55 | { 56 | int ret = -1; 57 | int ch; 58 | 59 | while ((ch = getopt(argc, argv, "s:")) != -1) { 60 | switch (ch) { 61 | case 's': 62 | socket_name = optarg; 63 | break; 64 | default: 65 | return usage(argv[0]); 66 | } 67 | } 68 | 69 | if (!socket_name) 70 | socket_name = strdup(UDEBUG_SOCK_NAME); 71 | 72 | signal(SIGPIPE, SIG_IGN); 73 | 74 | uloop_init(); 75 | 76 | unlink(socket_name); 77 | mkdir_sockdir(); 78 | umask(0111); 79 | server_fd.cb = server_fd_cb; 80 | server_fd.fd = usock(USOCK_UNIX | USOCK_SERVER | USOCK_NONBLOCK, socket_name, NULL); 81 | if (server_fd.fd < 0) { 82 | perror("usock"); 83 | goto out; 84 | } 85 | 86 | uloop_fd_add(&server_fd, ULOOP_READ); 87 | udebug_ubus_init(); 88 | uloop_run(); 89 | 90 | out: 91 | udebug_ubus_free(); 92 | unlink(socket_name); 93 | uloop_done(); 94 | 95 | return ret; 96 | } 97 | -------------------------------------------------------------------------------- /server.h: -------------------------------------------------------------------------------- 1 | #ifndef __UDEBUG_SERVER_H 2 | #define __UDEBUG_SERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | extern int debug_level; 12 | 13 | #define D(level, format, ...) \ 14 | do { \ 15 | if (debug_level >= level) \ 16 | fprintf(stderr, "DEBUG: %s(%d) " format "\n", \ 17 | __func__, __LINE__, ##__VA_ARGS__); \ 18 | } while (0) 19 | 20 | #define DC(level, cl, format, ...) \ 21 | D(level, "[%s(%d)] " format, cl->proc_name, cl->pid, ##__VA_ARGS__) 22 | 23 | struct client { 24 | struct list_head list; 25 | struct list_head bufs; 26 | struct uloop_fd fd; 27 | int notify_id; 28 | 29 | char proc_name[64]; 30 | int pid; 31 | int uid; 32 | 33 | int rx_fd; 34 | size_t rx_ofs; 35 | struct { 36 | struct udebug_client_msg msg; 37 | union { 38 | struct blob_attr data; 39 | uint8_t buf[4096]; 40 | }; 41 | } __attribute__((packed,aligned(4))) rx_buf; 42 | }; 43 | 44 | struct client_ring { 45 | struct list_head list; 46 | struct avl_node node; 47 | struct client *cl; 48 | 49 | int fd; 50 | uint32_t id; 51 | uint32_t ring_size, data_size; 52 | const char *name; 53 | struct blob_attr *flags; 54 | }; 55 | 56 | extern struct avl_tree rings; 57 | 58 | void client_alloc(int fd); 59 | struct client_ring *client_ring_alloc(struct client *cl); 60 | struct client_ring *client_ring_get_by_id(struct client *cl, uint32_t id); 61 | void client_ring_free(struct client_ring *r); 62 | 63 | static inline uint32_t ring_id(struct client_ring *r) 64 | { 65 | return (uint32_t)(uintptr_t)r->node.key; 66 | } 67 | 68 | static inline struct client_ring *ring_get_by_id(uint32_t id) 69 | { 70 | struct client_ring *r; 71 | void *key = (void *)(uintptr_t)id; 72 | 73 | return avl_find_element(&rings, key, r, node); 74 | } 75 | 76 | void udebug_ubus_init(void); 77 | void udebug_ubus_ring_notify(struct client_ring *r, bool add); 78 | void udebug_ubus_free(void); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /ring.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | static FILE *urandom; 4 | 5 | AVL_TREE(rings, udebug_id_cmp, true, NULL); 6 | 7 | struct client_ring *client_ring_get_by_id(struct client *cl, uint32_t id) 8 | { 9 | struct client_ring *r; 10 | 11 | list_for_each_entry(r, &cl->bufs, list) 12 | if (r->id == id) 13 | return r; 14 | 15 | return NULL; 16 | } 17 | 18 | static uint32_t gen_ring_id(void) 19 | { 20 | uint32_t val = 0; 21 | 22 | if (!urandom && (urandom = fopen("/dev/urandom", "r")) == NULL) 23 | return 0; 24 | 25 | if (fread(&val, sizeof(val), 1, urandom) != 1) 26 | return 0; 27 | 28 | return val; 29 | } 30 | 31 | struct client_ring *client_ring_alloc(struct client *cl) 32 | { 33 | enum { 34 | RING_ATTR_NAME, 35 | RING_ATTR_FLAGS, 36 | __RING_ATTR_MAX, 37 | }; 38 | static const struct blobmsg_policy policy[__RING_ATTR_MAX] = { 39 | [RING_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, 40 | [RING_ATTR_FLAGS] = { "flags", BLOBMSG_TYPE_ARRAY }, 41 | }; 42 | struct udebug_client_msg *msg = &cl->rx_buf.msg; 43 | struct blob_attr *tb[__RING_ATTR_MAX], *meta; 44 | struct client_ring *r; 45 | size_t meta_len; 46 | 47 | if (cl->rx_fd < 0) { 48 | DC(2, cl, "missing file descriptor"); 49 | return NULL; 50 | } 51 | 52 | meta_len = blob_pad_len(&cl->rx_buf.data); 53 | r = calloc_a(sizeof(*r), &meta, meta_len); 54 | memcpy(meta, cl->rx_buf.buf, meta_len); 55 | 56 | blobmsg_parse_attr(policy, __RING_ATTR_MAX, tb, meta); 57 | if (!tb[RING_ATTR_NAME]) { 58 | DC(2, cl, "missing ring name"); 59 | close(cl->rx_fd); 60 | free(r); 61 | return NULL; 62 | } 63 | 64 | r->name = blobmsg_get_string(tb[RING_ATTR_NAME]); 65 | r->flags = tb[RING_ATTR_FLAGS]; 66 | 67 | r->cl = cl; 68 | r->id = msg->id; 69 | r->fd = cl->rx_fd; 70 | cl->rx_fd = -1; 71 | r->ring_size = msg->ring_size; 72 | r->data_size = msg->data_size; 73 | list_add_tail(&r->list, &cl->bufs); 74 | 75 | r->node.key = (void *)(uintptr_t)gen_ring_id(); 76 | avl_insert(&rings, &r->node); 77 | udebug_ubus_ring_notify(r, true); 78 | DC(2, cl, "add ring %d [%x] ring_size=%x data_size=%x", r->id, ring_id(r), r->ring_size, r->data_size); 79 | 80 | return r; 81 | } 82 | 83 | void client_ring_free(struct client_ring *r) 84 | { 85 | DC(2, r->cl, "free ring %d [%x]", r->id, ring_id(r)); 86 | udebug_ubus_ring_notify(r, false); 87 | avl_delete(&rings, &r->node); 88 | list_del(&r->list); 89 | close(r->fd); 90 | free(r); 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # udebug - OpenWrt debugging infrastructure 2 | 3 | udebug assists whole-system debugging by making it easy to provide ring buffers 4 | with debug data and make them accessible through a unified API. 5 | Through the CLI, you can either create snapshots of data with a specific duration, 6 | or stream data in real time. The data itself is stored in .pcapng files, which can 7 | contain a mix of packets and log messages. 8 | 9 | ## Notes on using Wireshark 10 | 11 | In order to parse log messages in .pcapng files, you need to change the Wireshark 12 | configuration. 13 | Under `Preferences` -> `Protocols` -> `DLT_USER` -> `Encapsulations Table`, 14 | add an entry for `User 0 (DLT=147)` with Payload protocol `syslog`. 15 | 16 | ## libudebug C API 17 | 18 | #### `void udebug_init(struct udebug *ctx)` 19 | 20 | Initializes the udebug context. Must be called before adding buffers. 21 | 22 | #### `int udebug_connect(struct udebug *ctx, const char *path)` 23 | 24 | Connect to udebugd and submit any buffers that were added using `udebug_buf_add`. 25 | 26 | #### `void udebug_auto_connect(struct udebug *ctx, const char *path)` 27 | 28 | Connects and automatically reconnects to udebugd. Uses uloop and calls `udebug_add_uloop`. 29 | 30 | #### `void udebug_free(struct udebug *ctx)` 31 | 32 | Frees the udebug context and all added created buffers. 33 | 34 | #### `int udebug_buf_init(struct udebug_buf *buf, size_t entries, size_t size)` 35 | 36 | Allocates a buffer with a given size. Entries and size are rounded up internally to the 37 | nearest power-of-2. 38 | 39 | #### `int udebug_buf_add(struct udebug *ctx, struct udebug_buf *buf, const struct udebug_buf_meta *meta);` 40 | 41 | Submits the buffer to udebugd and makes it visible. 42 | 43 | #### `void udebug_buf_free(struct udebug_buf *buf)` 44 | 45 | Removes the buffer from udebugd and frees it. 46 | 47 | #### `void udebug_entry_init(struct udebug_buf *buf)` 48 | 49 | Initializes a new entry on the ring buffer. 50 | 51 | #### `void *udebug_entry_append(struct udebug_buf *buf, const void *data, uint32_t len)` 52 | 53 | Appends data to the ring buffer. When called with data == NULL, space is only 54 | reserved, and the return value provides a pointer with len bytes that can be 55 | written to. 56 | 57 | #### `int udebug_entry_printf(struct udebug_buf *buf, const char *fmt, ...)` 58 | 59 | Appends a string to the buffer, based on format string + arguments (like printf) 60 | 61 | #### `int udebug_entry_vprintf(struct udebug_buf *buf, const char *fmt, va_list ap)` 62 | 63 | Like `udebug_entry_printf()` 64 | 65 | #### `void udebug_entry_add(struct udebug_buf *buf)` 66 | 67 | Finalizes and publishes the entry on the ring buffer. 68 | 69 | ### Simple example 70 | 71 | ``` 72 | static struct udebug ud; 73 | static struct udebug_buf udb; 74 | 75 | /* ... */ 76 | 77 | uloop_init(); 78 | udebug_init(&ud); 79 | udebug_auto_connect(&ud, NULL); 80 | 81 | static const struct udebug_buf_meta buf_meta = { 82 | .name = "counter", 83 | .format = UDEBUG_FORMAT_STRING, 84 | }; 85 | 86 | int entries = 128; 87 | int data_size = 1024; 88 | 89 | udebug_buf_init(&udb, entries, data_size); 90 | udebug_buf_add(&ud, &udb, &buf_meta); 91 | 92 | /* ... */ 93 | 94 | udebug_entry_init(&udb); // initialize entry 95 | udebug_entry_printf(&udb, "count=%d", count++); 96 | udebug_entry_add(&udb); // finalize the entry 97 | 98 | ``` 99 | 100 | ## udebug CLI 101 | 102 | ``` 103 | Usage: udebug-cli [] [] 104 | 105 | Options: 106 | -f Ignore errors on opening rings 107 | -d : Only fetch data up to seconds old 108 | -o |- Set output file for snapshot/stream (or '-' for stdout) 109 | -i [:] Select debug buffer for snapshot/stream 110 | -s Use udebug socket 111 | -q Suppress warnings/error messages 112 | 113 | Commands: 114 | list: List available debug buffers 115 | snapshot: Create a pcapng snapshot of debug buffers 116 | set_flag [=0|1 ...] Set ring buffer flags 117 | get_flags Get ring buffer flags 118 | 119 | ``` 120 | 121 | -------------------------------------------------------------------------------- /lib.c: -------------------------------------------------------------------------------- 1 | #include "udebug.h" 2 | 3 | static struct blob_attr * 4 | find_attr(struct blob_attr *attr, const char *name, enum blobmsg_type type) 5 | { 6 | struct blobmsg_policy policy = { name, type }; 7 | struct blob_attr *ret; 8 | 9 | if (!attr) 10 | return NULL; 11 | 12 | blobmsg_parse_attr(&policy, 1, &ret, attr); 13 | return ret; 14 | } 15 | 16 | static void 17 | udebug_ubus_msg_cb(struct udebug_ubus *ctx, struct blob_attr *data) 18 | { 19 | struct blob_attr *en_attr; 20 | bool enabled; 21 | 22 | data = find_attr(data, "service", BLOBMSG_TYPE_TABLE); 23 | data = find_attr(data, ctx->service, BLOBMSG_TYPE_TABLE); 24 | if (!data) 25 | return; 26 | 27 | en_attr = find_attr(data, "enabled", BLOBMSG_TYPE_STRING); 28 | enabled = en_attr && !!atoi(blobmsg_get_string(en_attr)); 29 | ctx->cb(ctx, data, enabled); 30 | } 31 | 32 | static int 33 | udebug_ubus_notify_cb(struct ubus_context *ubus, struct ubus_object *obj, 34 | struct ubus_request_data *req, const char *method, 35 | struct blob_attr *msg) 36 | { 37 | struct udebug_ubus *ctx = container_of(obj, struct udebug_ubus, sub.obj); 38 | 39 | if (!strcmp(method, "config")) 40 | udebug_ubus_msg_cb(ctx, msg); 41 | 42 | return 0; 43 | } 44 | 45 | static void 46 | udebug_ubus_req_cb(struct ubus_request *req, int type, struct blob_attr *msg) 47 | { 48 | udebug_ubus_msg_cb(req->priv, msg); 49 | } 50 | 51 | static bool 52 | udebug_ubus_new_obj_cb(struct ubus_context *ubus, struct ubus_subscriber *sub, 53 | const char *path) 54 | { 55 | struct udebug_ubus *ctx = container_of(sub, struct udebug_ubus, sub); 56 | 57 | if (strcmp(path, "udebug") != 0) 58 | return false; 59 | 60 | uloop_timeout_set(&ctx->t, 1); 61 | return true; 62 | } 63 | 64 | static void udebug_ubus_get_config(struct uloop_timeout *t) 65 | { 66 | struct udebug_ubus *ctx = container_of(t, struct udebug_ubus, t); 67 | uint32_t id; 68 | 69 | if (ubus_lookup_id(ctx->ubus, "udebug", &id)) 70 | return; 71 | 72 | ubus_invoke(ctx->ubus, id, "get_config", NULL, udebug_ubus_req_cb, ctx, 1000); 73 | } 74 | 75 | void udebug_ubus_ring_init(struct udebug *ud, struct udebug_ubus_ring *ring) 76 | { 77 | if (!ring->size) 78 | ring->size = ring->default_size; 79 | if (!ring->entries) 80 | ring->entries = ring->default_entries; 81 | udebug_buf_init(ring->buf, ring->entries, ring->size); 82 | udebug_buf_add(ud, ring->buf, ring->meta); 83 | } 84 | 85 | void udebug_ubus_apply_config(struct udebug *ud, struct udebug_ubus_ring *rings, int n, 86 | struct blob_attr *data, bool enabled) 87 | { 88 | enum { 89 | CFG_ATTR_ENABLE, 90 | CFG_ATTR_SIZE, 91 | CFG_ATTR_ENTRIES, 92 | __CFG_ATTR_MAX, 93 | }; 94 | static struct blobmsg_policy policy[] = { 95 | [CFG_ATTR_ENABLE] = { NULL, BLOBMSG_TYPE_STRING }, 96 | [CFG_ATTR_SIZE] = { NULL, BLOBMSG_TYPE_STRING }, 97 | [CFG_ATTR_ENTRIES] = { NULL, BLOBMSG_TYPE_STRING }, 98 | }; 99 | 100 | for (size_t i = 0; i < n; i++) { 101 | struct blob_attr *tb[__CFG_ATTR_MAX], *cur; 102 | struct udebug_buf *buf = rings[i].buf; 103 | const char *name = rings[i].meta->name; 104 | int name_len = strlen(name); 105 | unsigned int size, entries; 106 | bool cur_enabled = enabled; 107 | char *str; 108 | 109 | policy[CFG_ATTR_ENABLE].name = name; 110 | 111 | #define SIZE_FMT "%s_size" 112 | str = alloca(sizeof(SIZE_FMT) + name_len); 113 | sprintf(str, SIZE_FMT, name); 114 | policy[CFG_ATTR_SIZE].name = str; 115 | 116 | #define ENTRIES_FMT "%s_entries" 117 | str = alloca(sizeof(ENTRIES_FMT) + name_len); 118 | sprintf(str, ENTRIES_FMT, name); 119 | policy[CFG_ATTR_ENTRIES].name = str; 120 | 121 | blobmsg_parse_attr(policy, __CFG_ATTR_MAX, tb, data); 122 | 123 | if (enabled && (cur = tb[CFG_ATTR_ENABLE]) != NULL) 124 | cur_enabled = !!atoi(blobmsg_get_string(cur)); 125 | 126 | if ((cur = tb[CFG_ATTR_SIZE]) != NULL) 127 | size = atoi(blobmsg_get_string(cur)); 128 | else 129 | size = rings[i].default_size; 130 | 131 | if ((cur = tb[CFG_ATTR_ENTRIES]) != NULL) 132 | entries = atoi(blobmsg_get_string(cur)); 133 | else 134 | entries = rings[i].default_entries; 135 | 136 | if (udebug_buf_valid(buf) == cur_enabled && 137 | size == rings[i].size && 138 | entries == rings[i].entries) 139 | continue; 140 | 141 | if (udebug_buf_valid(buf)) 142 | udebug_buf_free(buf); 143 | 144 | rings[i].size = size; 145 | rings[i].entries = entries; 146 | if (!cur_enabled) 147 | continue; 148 | 149 | udebug_ubus_ring_init(ud, &rings[i]); 150 | } 151 | } 152 | 153 | void udebug_netlink_msg(struct udebug_buf *buf, uint16_t proto, const void *data, size_t len) 154 | { 155 | struct { 156 | uint16_t pkttype; 157 | uint16_t arphdr; 158 | uint16_t _pad[5]; 159 | uint16_t proto; 160 | } hdr = { 161 | .arphdr = cpu_to_be16(824), 162 | .proto = cpu_to_be16(proto), 163 | }; 164 | 165 | if (!udebug_buf_valid(buf)) 166 | return; 167 | 168 | udebug_entry_init(buf); 169 | udebug_entry_append(buf, &hdr, sizeof(hdr)); 170 | udebug_entry_append(buf, data, len); 171 | udebug_entry_add(buf); 172 | } 173 | 174 | void udebug_ubus_init(struct udebug_ubus *ctx, struct ubus_context *ubus, 175 | const char *service, udebug_config_cb cb) 176 | { 177 | ctx->ubus = ubus; 178 | ctx->service = service; 179 | ctx->cb = cb; 180 | ctx->sub.new_obj_cb = udebug_ubus_new_obj_cb; 181 | ctx->sub.cb = udebug_ubus_notify_cb; 182 | ubus_register_subscriber(ubus, &ctx->sub); 183 | 184 | ctx->t.cb = udebug_ubus_get_config; 185 | } 186 | 187 | void udebug_ubus_free(struct udebug_ubus *ctx) 188 | { 189 | if (!ctx->ubus) 190 | return; 191 | 192 | uloop_timeout_cancel(&ctx->t); 193 | ubus_unregister_subscriber(ctx->ubus, &ctx->sub); 194 | } 195 | -------------------------------------------------------------------------------- /lib-pcap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "udebug-pcap.h" 9 | 10 | static char pcap_buf[65536]; 11 | static struct pcap_block_hdr *pcap_hdr = (struct pcap_block_hdr *)pcap_buf; 12 | 13 | struct pcap_block_hdr { 14 | uint32_t type; 15 | uint32_t len; 16 | }; 17 | 18 | struct pcap_shb_hdr { 19 | uint32_t endian; 20 | uint16_t major; 21 | uint16_t minor; 22 | uint64_t section_len; 23 | }; 24 | 25 | struct pcap_idb_hdr { 26 | uint16_t link_type; 27 | uint16_t _pad; 28 | uint32_t snap_len; 29 | }; 30 | 31 | struct pcap_epb_hdr { 32 | uint32_t iface; 33 | uint32_t ts_hi; 34 | uint32_t ts_lo; 35 | uint32_t cap_len; 36 | uint32_t pkt_len; 37 | }; 38 | 39 | struct pcap_opt_hdr { 40 | uint16_t id; 41 | uint16_t len; 42 | }; 43 | 44 | #define PCAPNG_BTYPE_SHB 0x0a0d0d0a 45 | #define PCAPNG_BTYPE_IDB 1 46 | #define PCAPNG_BTYPE_EPB 6 47 | 48 | #define PCAPNG_ENDIAN 0x1a2b3c4d 49 | 50 | static void * 51 | pcap_block_start(uint32_t type, uint32_t len) 52 | { 53 | struct pcap_block_hdr *b = pcap_hdr; 54 | 55 | b->type = type; 56 | b->len = len + sizeof(*b); 57 | memset(b + 1, 0, len); 58 | 59 | return b + 1; 60 | } 61 | 62 | static void * 63 | pcap_block_append(int len) 64 | { 65 | struct pcap_block_hdr *b = pcap_hdr; 66 | void *data = &pcap_buf[b->len]; 67 | 68 | memset(data, 0, len); 69 | b->len += len; 70 | 71 | return data; 72 | } 73 | 74 | static void * 75 | pcap_opt_append(int id, int len) 76 | { 77 | struct pcap_opt_hdr *opt; 78 | 79 | len = (len + 3) & ~3; 80 | opt = pcap_block_append(sizeof(*opt) + len); 81 | opt->id = id; 82 | opt->len = len; 83 | 84 | return opt + 1; 85 | } 86 | 87 | static void 88 | pcap_opt_str_add(int id, const char *val) 89 | { 90 | int len; 91 | 92 | if (!val) 93 | return; 94 | 95 | len = strlen(val) + 1; 96 | memcpy(pcap_opt_append(id, len), val, len); 97 | } 98 | 99 | static void 100 | pcap_opt_u8_add(uint16_t id, uint8_t val) 101 | { 102 | *(uint8_t *)pcap_opt_append(id, 1) = val; 103 | } 104 | 105 | static void 106 | pcap_opt_end(void) 107 | { 108 | pcap_block_append(4); 109 | } 110 | 111 | static uint32_t __pcap_block_align(int offset, int val) 112 | { 113 | struct pcap_block_hdr *b = pcap_hdr; 114 | uint32_t cur_len = b->len - offset; 115 | uint32_t aligned_len = (cur_len + (val - 1)) & ~(val - 1); 116 | uint32_t pad = aligned_len - cur_len; 117 | 118 | if (pad) 119 | pcap_block_append(pad); 120 | 121 | return pad; 122 | } 123 | 124 | static uint32_t pcap_block_align(int val) 125 | { 126 | return __pcap_block_align(0, val); 127 | } 128 | 129 | static int 130 | pcap_block_end(void) 131 | { 132 | struct pcap_block_hdr *b = (struct pcap_block_hdr *)pcap_buf; 133 | uint32_t *len; 134 | 135 | pcap_block_align(4); 136 | len = (uint32_t *)&pcap_buf[b->len]; 137 | b->len += 4; 138 | *len = b->len; 139 | 140 | return *len; 141 | } 142 | 143 | 144 | int pcap_init(struct pcap_context *p, struct pcap_meta *meta) 145 | { 146 | struct pcap_shb_hdr *shb; 147 | 148 | shb = pcap_block_start(PCAPNG_BTYPE_SHB, sizeof(*shb)); 149 | shb->endian = PCAPNG_ENDIAN; 150 | shb->major = 1; 151 | shb->section_len = ~0ULL; 152 | pcap_opt_str_add(2, meta->hw); 153 | pcap_opt_str_add(3, meta->os); 154 | pcap_opt_str_add(4, meta->app); 155 | pcap_opt_end(); 156 | pcap_block_end(); 157 | 158 | return 0; 159 | } 160 | 161 | int pcap_interface_init(struct pcap_context *p, uint32_t *id, 162 | struct pcap_interface_meta *meta) 163 | { 164 | struct pcap_idb_hdr *idb; 165 | 166 | *id = p->iface_id++; 167 | idb = pcap_block_start(PCAPNG_BTYPE_IDB, sizeof(*idb)); 168 | idb->link_type = meta->link_type; 169 | idb->snap_len = 0xffff; 170 | pcap_opt_str_add(2, meta->name); 171 | pcap_opt_str_add(3, meta->description); 172 | pcap_opt_u8_add(9, meta->time_res); 173 | pcap_opt_end(); 174 | pcap_block_end(); 175 | 176 | return 0; 177 | } 178 | 179 | void pcap_packet_init(uint32_t iface, uint64_t ts) 180 | { 181 | struct pcap_epb_hdr *epb; 182 | 183 | epb = pcap_block_start(PCAPNG_BTYPE_EPB, sizeof(*epb)); 184 | epb->iface = iface; 185 | epb->ts_hi = ts >> 32; 186 | epb->ts_lo = (uint32_t)ts; 187 | } 188 | 189 | void *pcap_packet_append(const void *data, size_t len) 190 | { 191 | void *buf; 192 | 193 | buf = pcap_block_append(len); 194 | if (data) 195 | memcpy(buf, data, len); 196 | 197 | return buf; 198 | } 199 | 200 | void pcap_packet_done(void) 201 | { 202 | struct pcap_epb_hdr *epb = (struct pcap_epb_hdr *)&pcap_hdr[1]; 203 | unsigned int len; 204 | 205 | len = pcap_hdr->len - sizeof(*pcap_hdr) - sizeof(*epb); 206 | epb->cap_len = epb->pkt_len = len; 207 | pcap_block_align(4); 208 | pcap_block_end(); 209 | } 210 | 211 | int pcap_interface_rbuf_init(struct pcap_context *p, struct udebug_remote_buf *rb) 212 | { 213 | const struct udebug_packet_info *meta = rb->meta; 214 | struct pcap_interface_meta if_meta = { 215 | .time_res = 6, 216 | .name = meta->attr[UDEBUG_META_IFACE_NAME], 217 | .description = meta->attr[UDEBUG_META_IFACE_DESC], 218 | }; 219 | 220 | if (rb->buf.hdr->format == UDEBUG_FORMAT_PACKET) 221 | if_meta.link_type = rb->buf.hdr->sub_format; 222 | else if (rb->buf.hdr->format == UDEBUG_FORMAT_STRING) 223 | if_meta.link_type = 147; 224 | 225 | return pcap_interface_init(p, &rb->pcap_iface, &if_meta); 226 | } 227 | 228 | int pcap_snapshot_packet_init(struct udebug *ctx, struct udebug_iter *it) 229 | { 230 | struct udebug_remote_buf *rb; 231 | struct udebug_snapshot *s = it->s; 232 | 233 | rb = udebug_remote_buf_get(ctx, s->rbuf_idx); 234 | if (!rb) 235 | return -1; 236 | 237 | pcap_packet_init(rb->pcap_iface, it->timestamp); 238 | 239 | switch (s->format) { 240 | case UDEBUG_FORMAT_PACKET: 241 | case UDEBUG_FORMAT_STRING: 242 | pcap_packet_append(it->data, it->len); 243 | break; 244 | case UDEBUG_FORMAT_BLOBMSG: 245 | break; 246 | default: 247 | return -1; 248 | } 249 | 250 | pcap_packet_done(); 251 | 252 | return 0; 253 | } 254 | 255 | bool pcap_block_write_file(FILE *f) 256 | { 257 | if (fwrite(pcap_buf, pcap_hdr->len, 1, f) != 1) 258 | return false; 259 | 260 | fflush(f); 261 | return true; 262 | } 263 | 264 | void *pcap_block_get(size_t *len) 265 | { 266 | *len = pcap_hdr->len; 267 | 268 | return pcap_buf; 269 | } 270 | -------------------------------------------------------------------------------- /ubus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "server.h" 4 | 5 | struct ubus_auto_conn conn; 6 | struct blob_buf b; 7 | static struct ubus_object udebug_object; 8 | static struct blob_attr *service_config; 9 | static struct blob_attr *service_config_override; 10 | 11 | enum { 12 | LIST_ATTR_PROCNAME, 13 | LIST_ATTR_RINGNAME, 14 | LIST_ATTR_PID, 15 | LIST_ATTR_UID, 16 | __LIST_ATTR_MAX, 17 | }; 18 | 19 | static const struct blobmsg_policy list_policy[__LIST_ATTR_MAX] = { 20 | [LIST_ATTR_PROCNAME] = { "proc_name", BLOBMSG_TYPE_ARRAY }, 21 | [LIST_ATTR_RINGNAME] = { "ring_name", BLOBMSG_TYPE_ARRAY }, 22 | [LIST_ATTR_PID] = { "pid", BLOBMSG_TYPE_ARRAY }, 23 | [LIST_ATTR_UID] = { "uid", BLOBMSG_TYPE_ARRAY }, 24 | }; 25 | 26 | static bool 27 | string_array_match(const char *val, struct blob_attr *match) 28 | { 29 | struct blob_attr *cur; 30 | int rem; 31 | 32 | if (!match || !blobmsg_len(match)) 33 | return true; 34 | 35 | if (blobmsg_check_array(match, BLOBMSG_TYPE_STRING) < 0) 36 | return false; 37 | 38 | blobmsg_for_each_attr(cur, match, rem) { 39 | if (fnmatch(blobmsg_get_string(cur), val, 0) == 0) 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | 46 | static bool 47 | int_array_match(unsigned int val, struct blob_attr *match) 48 | { 49 | struct blob_attr *cur; 50 | int rem; 51 | 52 | if (!match || !blobmsg_len(match)) 53 | return true; 54 | 55 | if (blobmsg_check_array(match, BLOBMSG_TYPE_INT32) < 0) 56 | return false; 57 | 58 | blobmsg_for_each_attr(cur, match, rem) { 59 | if (val == blobmsg_get_u32(cur)) 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | static bool 67 | udebug_list_match(struct client_ring *r, struct blob_attr **tb) 68 | { 69 | return string_array_match(r->cl->proc_name, tb[LIST_ATTR_PROCNAME]) && 70 | string_array_match(r->name, tb[LIST_ATTR_RINGNAME]) && 71 | int_array_match(r->cl->pid, tb[LIST_ATTR_PID]) && 72 | int_array_match(r->cl->uid, tb[LIST_ATTR_UID]); 73 | } 74 | 75 | static void 76 | udebug_list_add_ring_data(struct client_ring *r) 77 | { 78 | blobmsg_add_u32(&b, "id", ring_id(r)); 79 | blobmsg_add_string(&b, "proc_name", r->cl->proc_name); 80 | blobmsg_add_string(&b, "ring_name", r->name); 81 | blobmsg_add_u32(&b, "pid", r->cl->pid); 82 | blobmsg_add_u32(&b, "uid", r->cl->uid); 83 | blobmsg_add_u32(&b, "ring_size", r->ring_size); 84 | blobmsg_add_u32(&b, "data_size", r->data_size); 85 | if (r->flags) 86 | blobmsg_add_blob(&b, r->flags); 87 | } 88 | 89 | void udebug_ubus_ring_notify(struct client_ring *r, bool add) 90 | { 91 | blob_buf_init(&b, 0); 92 | udebug_list_add_ring_data(r); 93 | ubus_notify(&conn.ctx, &udebug_object, add ? "add" : "remove", b.head, -1); 94 | } 95 | 96 | static void 97 | udebug_list_add_ring(struct client_ring *r) 98 | { 99 | void *c; 100 | 101 | c = blobmsg_open_table(&b, NULL); 102 | udebug_list_add_ring_data(r); 103 | blobmsg_close_table(&b, c); 104 | } 105 | 106 | static int 107 | udebug_list(struct ubus_context *ctx, struct ubus_object *obj, 108 | struct ubus_request_data *req, const char *method, 109 | struct blob_attr *msg) 110 | { 111 | struct blob_attr *tb[__LIST_ATTR_MAX]; 112 | struct client_ring *r; 113 | void *c; 114 | 115 | blobmsg_parse_attr(list_policy, __LIST_ATTR_MAX, tb, msg); 116 | 117 | blob_buf_init(&b, 0); 118 | c = blobmsg_open_array(&b, "results"); 119 | avl_for_each_element(&rings, r, node) 120 | if (udebug_list_match(r, tb)) 121 | udebug_list_add_ring(r); 122 | blobmsg_close_array(&b, c); 123 | ubus_send_reply(ctx, req, b.head); 124 | 125 | return 0; 126 | } 127 | 128 | enum { 129 | CFG_ATTR_OVERRIDE, 130 | CFG_ATTR_SERVICE, 131 | __CFG_ATTR_MAX 132 | }; 133 | static const struct blobmsg_policy config_policy[__CFG_ATTR_MAX] = { 134 | [CFG_ATTR_OVERRIDE] = { "override", BLOBMSG_TYPE_BOOL }, 135 | [CFG_ATTR_SERVICE] = { "service", BLOBMSG_TYPE_TABLE }, 136 | }; 137 | 138 | static struct blob_attr * 139 | udebug_fill_config(int override) 140 | { 141 | struct blob_attr *config; 142 | 143 | if (override < 0) 144 | config = service_config_override ? : service_config; 145 | else if (override) 146 | config = service_config_override; 147 | else 148 | config = service_config; 149 | 150 | blob_buf_init(&b, 0); 151 | if (config) 152 | blobmsg_add_blob(&b, config); 153 | else 154 | blobmsg_close_table(&b, blobmsg_open_table(&b, "service")); 155 | 156 | return b.head; 157 | } 158 | 159 | static int 160 | udebug_set_config(struct ubus_context *ctx, struct ubus_object *obj, 161 | struct ubus_request_data *req, const char *method, 162 | struct blob_attr *msg) 163 | { 164 | struct blob_attr *tb[__CFG_ATTR_MAX], *cur; 165 | struct blob_attr **dest = &service_config; 166 | 167 | blobmsg_parse_attr(config_policy, __CFG_ATTR_MAX, tb, msg); 168 | if ((cur = tb[CFG_ATTR_OVERRIDE]) != NULL && 169 | blobmsg_get_bool(cur)) 170 | dest = &service_config_override; 171 | 172 | if ((cur = tb[CFG_ATTR_SERVICE]) != NULL) { 173 | free(*dest); 174 | *dest = blob_memdup(cur); 175 | } else if (dest == &service_config_override) { 176 | free(*dest); 177 | *dest = NULL; 178 | } 179 | 180 | if (dest != &service_config || !service_config_override) 181 | ubus_notify(ctx, obj, "config", udebug_fill_config(-1), -1); 182 | 183 | return 0; 184 | } 185 | 186 | static int 187 | udebug_get_config(struct ubus_context *ctx, struct ubus_object *obj, 188 | struct ubus_request_data *req, const char *method, 189 | struct blob_attr *msg) 190 | { 191 | struct blob_attr *tb[__CFG_ATTR_MAX], *cur; 192 | int override = -1; 193 | 194 | blobmsg_parse_attr(config_policy, __CFG_ATTR_MAX, tb, msg); 195 | if ((cur = tb[CFG_ATTR_OVERRIDE]) != NULL) 196 | override = blobmsg_get_bool(cur); 197 | 198 | ubus_send_reply(ctx, req, udebug_fill_config(override)); 199 | 200 | return 0; 201 | } 202 | 203 | static const struct ubus_method udebug_methods[] = { 204 | UBUS_METHOD("list", udebug_list, list_policy), 205 | UBUS_METHOD("set_config", udebug_set_config, config_policy), 206 | UBUS_METHOD_MASK("get_config", udebug_get_config, config_policy, 207 | 1 << CFG_ATTR_OVERRIDE), 208 | }; 209 | 210 | static struct ubus_object_type udebug_object_type = 211 | UBUS_OBJECT_TYPE("udebug", udebug_methods); 212 | 213 | static struct ubus_object udebug_object = { 214 | .name = "udebug", 215 | .type = &udebug_object_type, 216 | .methods = udebug_methods, 217 | .n_methods = ARRAY_SIZE(udebug_methods), 218 | }; 219 | 220 | static void ubus_connect_cb(struct ubus_context *ctx) 221 | { 222 | ubus_add_object(ctx, &udebug_object); 223 | } 224 | 225 | void udebug_ubus_init(void) 226 | { 227 | conn.cb = ubus_connect_cb; 228 | ubus_auto_connect(&conn); 229 | } 230 | 231 | void udebug_ubus_free(void) 232 | { 233 | ubus_auto_shutdown(&conn); 234 | } 235 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef linux 14 | #include 15 | #endif 16 | #ifdef __APPLE__ 17 | #include 18 | #endif 19 | 20 | #include "server.h" 21 | 22 | #define UDEBUG_SNDBUF 65536 23 | 24 | static LIST_HEAD(clients); 25 | 26 | static void client_send_msg(struct client *cl, struct udebug_client_msg *data, int fd) 27 | { 28 | uint8_t fd_buf[CMSG_SPACE(sizeof(int))] = { 0 }; 29 | struct iovec iov = { 30 | .iov_base = data, 31 | .iov_len = sizeof(*data), 32 | }; 33 | struct msghdr msg = { 34 | .msg_control = fd_buf, 35 | .msg_controllen = sizeof(fd_buf), 36 | .msg_iov = &iov, 37 | .msg_iovlen = 1, 38 | }; 39 | struct cmsghdr *cmsg; 40 | int buffered = 0; 41 | int *pfd; 42 | int len; 43 | 44 | #ifdef linux 45 | ioctl(cl->fd.fd, SIOCOUTQ, &buffered); 46 | #elif defined(__APPLE__) 47 | socklen_t slen = sizeof(buffered); 48 | getsockopt(cl->fd.fd, SOL_SOCKET, SO_NWRITE, &buffered, &slen); 49 | #endif 50 | 51 | DC(3, cl, "send msg type=%d len=%d, fd=%d", 52 | data->type, (unsigned int)iov.iov_len, fd); 53 | 54 | if (buffered > UDEBUG_SNDBUF / 2) { 55 | DC(3, cl, "skip message due to limited buffer size"); 56 | return; 57 | } 58 | 59 | if (fd >= 0) { 60 | cmsg = CMSG_FIRSTHDR(&msg); 61 | cmsg->cmsg_type = SCM_RIGHTS; 62 | cmsg->cmsg_level = SOL_SOCKET; 63 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 64 | msg.msg_controllen = cmsg->cmsg_len; 65 | 66 | pfd = (int *)CMSG_DATA(cmsg); 67 | *pfd = fd; 68 | } else { 69 | msg.msg_control = NULL; 70 | msg.msg_controllen = 0; 71 | } 72 | 73 | do { 74 | len = sendmsg(cl->fd.fd, &msg, 0); 75 | } while (len < 0 && errno == EINTR); 76 | } 77 | 78 | static int client_alloc_notify_id(void) 79 | { 80 | struct client *cl; 81 | uint32_t mask = 0; 82 | 83 | list_for_each_entry(cl, &clients, list) 84 | if (cl->notify_id >= 0) 85 | mask |= 1 << cl->notify_id; 86 | 87 | for (int i = 0; i < 32; i++, mask >>= 1) 88 | if (!(mask & 1)) 89 | return i; 90 | 91 | return 31; 92 | } 93 | 94 | static void client_msg_get_handle(struct client *cl) 95 | { 96 | struct udebug_client_msg msg = { 97 | .type = CL_MSG_GET_HANDLE, 98 | }; 99 | 100 | if (cl->notify_id < 0 && cl->uid == 0) 101 | cl->notify_id = client_alloc_notify_id(); 102 | 103 | msg.id = cl->notify_id; 104 | client_send_msg(cl, &msg, -1); 105 | } 106 | 107 | static void client_msg_ring_get(struct client *cl, uint32_t id) 108 | { 109 | struct udebug_client_msg msg = { 110 | .type = CL_MSG_RING_GET, 111 | .id = id, 112 | }; 113 | struct client_ring *r = ring_get_by_id(id); 114 | int fd = -1; 115 | 116 | if (!r || cl->uid != 0) { 117 | DC(2, cl, "could not get ring %x", id); 118 | goto out; 119 | } 120 | 121 | fd = r->fd; 122 | msg.ring_size = r->ring_size; 123 | msg.data_size = r->data_size; 124 | 125 | out: 126 | client_send_msg(cl, &msg, fd); 127 | } 128 | 129 | static void client_msg_notify(struct client_ring *r, uint32_t mask) 130 | { 131 | struct udebug_client_msg msg = { 132 | .type = CL_MSG_RING_NOTIFY, 133 | .id = ring_id(r), 134 | .notify_mask = mask, 135 | }; 136 | struct client *cl; 137 | 138 | list_for_each_entry(cl, &clients, list) { 139 | if (cl->notify_id < 0 || 140 | !(mask & (1 << cl->notify_id))) 141 | continue; 142 | 143 | client_send_msg(cl, &msg, -1); 144 | } 145 | } 146 | 147 | static void client_free(struct client *cl) 148 | { 149 | struct client_ring *r; 150 | 151 | while (!list_empty(&cl->bufs)) { 152 | r = list_first_entry(&cl->bufs, struct client_ring, list); 153 | client_ring_free(r); 154 | } 155 | 156 | DC(2, cl, "disconnect"); 157 | uloop_fd_delete(&cl->fd); 158 | close(cl->fd.fd); 159 | list_del(&cl->list); 160 | 161 | free(cl); 162 | } 163 | 164 | static void client_parse_message(struct client *cl) 165 | { 166 | struct udebug_client_msg *msg = &cl->rx_buf.msg; 167 | struct client_ring *r; 168 | 169 | DC(3, cl, "msg type=%d len=%d", msg->type, (unsigned int)cl->rx_ofs); 170 | switch (msg->type) { 171 | case CL_MSG_RING_ADD: 172 | client_ring_alloc(cl); 173 | client_send_msg(cl, msg, -1); 174 | break; 175 | case CL_MSG_RING_REMOVE: 176 | DC(2, cl, "delete ring %x", msg->id); 177 | r = client_ring_get_by_id(cl, msg->id); 178 | if (r) 179 | client_ring_free(r); 180 | else 181 | DC(2, cl, "ring not found"); 182 | client_send_msg(cl, msg, -1); 183 | break; 184 | case CL_MSG_RING_NOTIFY: 185 | DC(3, cl, "notify on ring %d", msg->id); 186 | r = client_ring_get_by_id(cl, msg->id); 187 | if (r) 188 | client_msg_notify(r, msg->notify_mask); 189 | else 190 | DC(2, cl, "local ring %d not found", msg->id); 191 | break; 192 | case CL_MSG_GET_HANDLE: 193 | client_msg_get_handle(cl); 194 | DC(2, cl, "get notify handle: %d", cl->notify_id); 195 | break; 196 | case CL_MSG_RING_GET: 197 | DC(2, cl, "get ring %x", msg->id); 198 | client_msg_ring_get(cl, msg->id); 199 | break; 200 | default: 201 | DC(3, cl, "Invalid message type %d", msg->type); 202 | break; 203 | } 204 | 205 | if (cl->rx_fd < 0) 206 | return; 207 | 208 | close(cl->rx_fd); 209 | cl->rx_fd = -1; 210 | } 211 | 212 | static void client_fd_cb(struct uloop_fd *fd, unsigned int events) 213 | { 214 | struct client *cl = container_of(fd, struct client, fd); 215 | uint8_t fd_buf[CMSG_SPACE(sizeof(int))] = {}; 216 | struct iovec iov = {}; 217 | struct msghdr msg = { 218 | .msg_iov = &iov, 219 | .msg_iovlen = 1, 220 | .msg_control = fd_buf, 221 | .msg_controllen = sizeof(fd_buf), 222 | }; 223 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 224 | size_t min_sz = sizeof(cl->rx_buf.msg) + sizeof(struct blob_attr); 225 | ssize_t len; 226 | int *pfd; 227 | 228 | cmsg->cmsg_type = SCM_RIGHTS; 229 | cmsg->cmsg_level = SOL_SOCKET; 230 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 231 | 232 | pfd = (int *)CMSG_DATA(cmsg); 233 | msg.msg_controllen = cmsg->cmsg_len; 234 | 235 | retry: 236 | *pfd = -1; 237 | if (fd->eof) { 238 | client_free(cl); 239 | return; 240 | } 241 | 242 | iov.iov_base = &cl->rx_buf; 243 | iov.iov_len = min_sz; 244 | if (!cl->rx_ofs) { 245 | iov.iov_base = &cl->rx_buf.msg; 246 | iov.iov_len = min_sz; 247 | 248 | len = recvmsg(fd->fd, &msg, 0); 249 | if (len < 0) 250 | return; 251 | if (!len) 252 | fd->eof = true; 253 | 254 | cl->rx_ofs = len; 255 | cl->rx_fd = *pfd; 256 | goto retry; 257 | } else if (cl->rx_ofs >= min_sz) { 258 | iov.iov_len += blob_pad_len(&cl->rx_buf.data); 259 | iov.iov_len -= sizeof(struct blob_attr); 260 | if (iov.iov_len > sizeof(cl->rx_buf)) { 261 | client_free(cl); 262 | return; 263 | } 264 | } 265 | 266 | iov.iov_base += cl->rx_ofs; 267 | iov.iov_len -= cl->rx_ofs; 268 | if (iov.iov_len) { 269 | len = read(fd->fd, iov.iov_base, iov.iov_len); 270 | if (len <= 0) 271 | return; 272 | 273 | cl->rx_ofs += len; 274 | goto retry; 275 | } 276 | 277 | client_parse_message(cl); 278 | cl->rx_ofs = 0; 279 | goto retry; 280 | } 281 | 282 | static void client_get_info(struct client *cl) 283 | { 284 | #ifdef LOCAL_PEERPID 285 | socklen_t len = sizeof(&cl->pid); 286 | if (getsockopt(cl->fd.fd, SOL_LOCAL, LOCAL_PEERPID, &cl->pid, &len) < 0) 287 | return; 288 | #elif defined(SO_PEERCRED) 289 | struct ucred uc; 290 | socklen_t len = sizeof(uc); 291 | if (getsockopt(cl->fd.fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0) 292 | return; 293 | cl->pid = uc.pid; 294 | cl->uid = uc.uid; 295 | #endif 296 | } 297 | 298 | static void client_get_procname(struct client *cl) 299 | { 300 | #ifdef linux 301 | char buf[256]; 302 | FILE *f; 303 | 304 | snprintf(buf, sizeof(buf), "/proc/%d/cmdline", cl->pid); 305 | f = fopen(buf, "r"); 306 | if (!f) 307 | return; 308 | buf[fread(buf, 1, sizeof(buf) - 1, f)] = 0; 309 | fclose(f); 310 | snprintf(cl->proc_name, sizeof(cl->proc_name), "%s", basename(buf)); 311 | #endif 312 | #ifdef __APPLE__ 313 | proc_name(cl->pid, cl->proc_name, sizeof(cl->proc_name) - 1); 314 | #endif 315 | } 316 | 317 | void client_alloc(int fd) 318 | { 319 | int sndbuf = UDEBUG_SNDBUF; 320 | struct client *cl; 321 | 322 | setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); 323 | 324 | cl = calloc(1, sizeof(*cl)); 325 | INIT_LIST_HEAD(&cl->bufs); 326 | cl->fd.fd = fd; 327 | cl->fd.cb = client_fd_cb; 328 | cl->rx_fd = -1; 329 | client_get_info(cl); 330 | if (cl->pid) 331 | client_get_procname(cl); 332 | if (!cl->proc_name[0]) 333 | snprintf(cl->proc_name, sizeof(cl->proc_name), ""); 334 | 335 | DC(2, cl, "connect"); 336 | uloop_fd_add(&cl->fd, ULOOP_READ); 337 | list_add_tail(&cl->list, &clients); 338 | } 339 | -------------------------------------------------------------------------------- /udebug-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ucode 2 | 'use strict'; 3 | import { basename, readlink, open, stdout } from "fs"; 4 | let udebug = require("udebug"); 5 | let uloop = require("uloop"); 6 | let libubus = require("ubus"); 7 | uloop.init(); 8 | let ubus = libubus.connect(); 9 | 10 | let opts = { 11 | select: [] 12 | }; 13 | let service_config; 14 | let trace_config; 15 | 16 | const usage_message = ` 17 | Usage: ${basename(sourcepath())} [] [] 18 | 19 | Options: 20 | -f Ignore errors on opening rings 21 | -e List of services to enable before running the command 22 | -d : Only fetch data up to seconds old 23 | -o |- Set output file for snapshot/stream (or '-' for stdout) 24 | -i [:] Select debug buffer for snapshot/stream 25 | -s Use udebug socket 26 | -q Suppress warnings/error messages 27 | -l Select only debug buffers containing log messages 28 | -t Include timestamps in logstream output 29 | 30 | Commands: 31 | reset: Reset enabled debug buffers to configured settings 32 | set : Set enabled debug buffers 33 | list: List available debug buffers 34 | snapshot: Create a pcapng snapshot of debug buffers 35 | set_flag [=0|1 ...] Set ring buffer flags 36 | get_flags Get ring buffer flags 37 | stream: Stream packet data as pcap 38 | logstream: Stream syslog data as text 39 | 40 | Service list: space separated list of services matching the config 41 | - Enable service 42 | - .= Set service option to 43 | 44 | `; 45 | 46 | function _warn(str) { 47 | if (opts.quiet) 48 | return; 49 | 50 | warn(str); 51 | } 52 | 53 | function usage() { 54 | warn(usage_message); 55 | exit(1); 56 | } 57 | 58 | function parse_service_value(svc, option, val) 59 | { 60 | if (option) { 61 | svc[option] = val; 62 | val = '1'; 63 | } 64 | 65 | svc.enabled = val; 66 | } 67 | 68 | function parse_service_entry(name, option, val) 69 | { 70 | if (wildcard(name, "kernel:*")) { 71 | name = substr(name, 7); 72 | trace_config ??= []; 73 | push(trace_config, name); 74 | return; 75 | } 76 | 77 | if (index(name, "*") >= 0) { 78 | for (let svcname, svc in service_config) { 79 | if (!wildcard(svcname, name)) 80 | continue; 81 | 82 | parse_service_value(svc, option, val); 83 | } 84 | return; 85 | } 86 | 87 | let svc = service_config[name]; 88 | if (svc) { 89 | parse_service_value(svc, option, val); 90 | return; 91 | } 92 | 93 | if (!option && !+val) 94 | return; 95 | 96 | svc = service_config[name] = { 97 | enabled: "1" 98 | }; 99 | 100 | if (option) 101 | parse_service_value(svc, option, val); 102 | } 103 | 104 | function parse_service_list(val) { 105 | if (!service_config) { 106 | service_config = ubus.call('udebug', 'get_config', { override: false })?.service; 107 | if (!service_config) { 108 | _warn('Failed to get current service config'); 109 | exit(1); 110 | } 111 | for (let name, svc in service_config) 112 | svc.enabled = "0"; 113 | } 114 | 115 | let val_list = split(val, /[ \t]+/); 116 | for (let cur in val_list) { 117 | cur = split(cur, "="); 118 | let val = cur[1] ?? "1"; 119 | 120 | cur = split(cur[0], ".", 2); 121 | parse_service_entry(cur[0], cur[1], val); 122 | } 123 | } 124 | 125 | while (substr(ARGV[0], 0, 1) == "-") { 126 | let opt = substr(shift(ARGV), 1); 127 | switch(opt) { 128 | case 'd': 129 | opts.duration = +shift(ARGV); 130 | break; 131 | case 's': 132 | opts.socket = shift(ARGV); 133 | break; 134 | case 'i': 135 | push(opts.select, shift(ARGV)); 136 | break; 137 | case 'o': 138 | opts.output_file = shift(ARGV); 139 | break; 140 | case 'e': 141 | parse_service_list(shift(ARGV)); 142 | break; 143 | case 'q': 144 | opts.quiet = true; 145 | break; 146 | case 'f': 147 | opts.force = true; 148 | break; 149 | case 'l': 150 | opts.log_only = true; 151 | break; 152 | case 't': 153 | opts.timestamp = true; 154 | break; 155 | default: 156 | usage(); 157 | } 158 | } 159 | 160 | let procs = {}; 161 | let selected = []; 162 | let rings = {}; 163 | let subscriber; 164 | let pcap, log_out; 165 | 166 | function ring_selected(ring) { 167 | if (!length(opts.select)) 168 | return true; 169 | 170 | for (let sel in opts.select) { 171 | let match = split(sel, ":", 2); 172 | if (wildcard(ring.proc_name, match[0]) && 173 | (!match[1] || wildcard(ring.ring_name, match[1]))) 174 | return true; 175 | } 176 | 177 | return false; 178 | } 179 | 180 | function poll_data() { 181 | let data = []; 182 | for (let ring_id in rings) { 183 | let ring = rings[ring_id]; 184 | let s = ring[1].fetch(); 185 | if (s) 186 | push(data, s); 187 | } 188 | if (length(data) > 0) { 189 | if (log_out) { 190 | udebug.foreach_packet(data, (entry, data, timestamp) => { 191 | if (!length(data)) 192 | return; 193 | if ((opts.timestamp && !log_out.write(sprintf("[%.6f] ", timestamp / 1000000.0))) || 194 | !log_out.write(data) || !log_out.write("\n")) 195 | uloop.end(); 196 | log_out.flush(); 197 | }); 198 | } 199 | if (pcap && pcap.write(data) == null) 200 | uloop.end(); 201 | } 202 | } 203 | 204 | function open_ring(ring, poll) { 205 | let ring_name =` ${ring.proc_name}:${ring.ring_name}`; 206 | let ref = udebug.get_ring(ring); 207 | 208 | if (!ref) 209 | return null; 210 | if (opts.log_only && ref.get_info().format != udebug.FORMAT_STRING) 211 | return false; 212 | if (opts.duration) 213 | ref.set_fetch_duration(opts.duration); 214 | if (poll) 215 | ref.set_poll_cb(() => { poll_data() }); 216 | 217 | let ring_id = ring.id + ""; 218 | ring = [ ring_name, ref ]; 219 | rings[ring_id] = ring; 220 | 221 | return ring; 222 | } 223 | 224 | function open_trace() { 225 | let ring = udebug.trace_ring("udebug-" + readlink("/proc/self")); 226 | 227 | ring.set_file("trace_clock", "tai"); 228 | ring.set_file("set_event", join("\n", trace_config)); 229 | ring.set_poll_cb(poll_data); 230 | rings.kernel = [ "kernel", ring ]; 231 | } 232 | 233 | function open_log_out() { 234 | let out = opts.output_file; 235 | if (!opts.output_file || out == "-") 236 | log_out = stdout; 237 | else 238 | log_out = open(opts.output_file, "w"); 239 | 240 | if (!log_out) { 241 | _warn(`Could not open output file\n`); 242 | exit(1); 243 | } 244 | } 245 | 246 | function open_pcap_out() { 247 | if (!opts.output_file) { 248 | _warn(`No output file\n`); 249 | exit(1); 250 | } 251 | let out = opts.output_file; 252 | if (out == "-") 253 | out = null; 254 | 255 | pcap = udebug.pcap_file(out); 256 | if (!pcap) { 257 | _warn(`Failed to open output\n`); 258 | exit(1); 259 | } 260 | } 261 | 262 | function stream_data(log) { 263 | if (log) 264 | open_log_out(); 265 | else 266 | open_pcap_out(); 267 | 268 | subscriber = ubus.subscriber((req) => { 269 | let type = req.type; 270 | let ring = req.data; 271 | let ring_id = ring.id + ""; 272 | if (type == "remove") { 273 | ring = rings[ring_id]; 274 | if (!ring) 275 | return; 276 | 277 | ring[1].close(); 278 | delete rings[ring_id]; 279 | } else if (type == "add") { 280 | open_ring(ring, true); 281 | poll_data(); 282 | } 283 | }, null, [ "udebug" ]); 284 | for (let ring in selected) { 285 | if (open_ring(ring, true) == null) { 286 | _warn(`Failed to open ring ${ring_name}\n`); 287 | if (opts.force) 288 | continue; 289 | 290 | exit(1); 291 | } 292 | } 293 | 294 | if (trace_config) 295 | open_trace(); 296 | 297 | let done = () => { uloop.end(); }; 298 | signal('SIGINT', done); 299 | signal('SIGTERM', done); 300 | 301 | poll_data(); 302 | delete opts.duration; 303 | uloop.run(); 304 | } 305 | 306 | let cmds = { 307 | list: function() { 308 | for (let proc in procs) { 309 | print(`Process ${proc}:\n`); 310 | for (let ring in procs[proc]) 311 | print(` - ${ring.ring_name}\n`); 312 | } 313 | }, 314 | snapshot: function() { 315 | open_pcap_out(); 316 | 317 | if (!length(selected)) { 318 | _warn(`No available debug buffers\n`); 319 | exit(1); 320 | } 321 | 322 | for (let ring in selected) { 323 | if (open_ring(ring) == null) { 324 | _warn(`Failed to open ring ${ring.proc_name}:${ring.ring_name}\n`); 325 | if (opts.force) 326 | continue; 327 | 328 | exit(1); 329 | } 330 | } 331 | 332 | poll_data(); 333 | pcap.close(); 334 | }, 335 | set_flag: function() { 336 | for (let ring in selected) { 337 | if (!length(ring.flags)) 338 | continue; 339 | 340 | let mask = 0, set = 0; 341 | for (let flag in ring.flags) { 342 | for (let change in ARGV) { 343 | change = split(change, "=", 2); 344 | let name = change[0]; 345 | let val = !!int(change[1]); 346 | if (flag[0] == name) 347 | if (val) 348 | set |= flag[1]; 349 | else 350 | mask |= flag[1]; 351 | } 352 | } 353 | 354 | if (!(mask | set)) 355 | continue; 356 | 357 | let r = open_ring(ring); 358 | if (!r) 359 | continue; 360 | 361 | r[1].change_flags(mask, set); 362 | } 363 | }, 364 | get_flags: function() { 365 | for (let ring in selected) { 366 | if (!length(ring.flags)) 367 | continue; 368 | 369 | let r = open_ring(ring); 370 | if (!r) 371 | continue; 372 | 373 | print(`${r[0]}\n`); 374 | let flags = r[1].get_flags(); 375 | for (let flag in ring.flags) 376 | print(`\t${flag[0]}=${((flags & flag[1]) == flag[1]) ? 1 : 0 }\n`); 377 | } 378 | }, 379 | stream: function() { 380 | stream_data(false); 381 | }, 382 | logstream: function() { 383 | stream_data(true); 384 | }, 385 | reset: function() { 386 | exit(0); 387 | }, 388 | set: function() { 389 | exit(0); 390 | }, 391 | }; 392 | 393 | let cmd = shift(ARGV); 394 | if (!cmds[cmd]) 395 | usage(); 396 | 397 | if (cmd == "logstream") 398 | opts.log_only = true; 399 | 400 | if (cmd == 'reset') { 401 | ubus.call('udebug', 'set_config', { override: true }); 402 | exit(0); 403 | } 404 | 405 | if (cmd == 'set') { 406 | if (!length(ARGV)) 407 | usage(); 408 | for (let val in ARGV) 409 | parse_service_list(val); 410 | } 411 | 412 | if (service_config) { 413 | ubus.call('udebug', 'set_config', { 414 | override: true, 415 | service: service_config, 416 | }); 417 | } 418 | 419 | if (service_config && cmd != 'set') 420 | sleep(1000); 421 | 422 | let ring_list = ubus.call("udebug", "list"); 423 | if (!ring_list || !ring_list.results) { 424 | warn("Failed to get ring buffer list from udebugd\n"); 425 | exit(1); 426 | } 427 | 428 | ring_list = ring_list.results; 429 | for (let ring in ring_list) { 430 | if (!ring_selected(ring)) 431 | continue; 432 | 433 | let proc = procs[ring.proc_name]; 434 | if (!proc) { 435 | proc = []; 436 | procs[ring.proc_name] = proc; 437 | } 438 | push(proc, ring); 439 | push(selected, ring); 440 | } 441 | 442 | if (cmd != "list" && !udebug.init(opts.socket)) { 443 | _warn(`Failed to connect to udebug socket\n`); 444 | exit(1); 445 | } 446 | 447 | cmds[cmd](); 448 | -------------------------------------------------------------------------------- /lib-ucode.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "udebug-pcap.h" 11 | 12 | #define TRACE_RING_ID 0x7fffffff 13 | 14 | static struct udebug u; 15 | static bool init_done; 16 | static int trace_dirfd = -1; 17 | 18 | static struct udebug_hdr trace_hdr = { 19 | .format = UDEBUG_FORMAT_STRING, 20 | }; 21 | static struct udebug_packet_info trace_meta = { 22 | .attr = { 23 | [UDEBUG_META_IFACE_NAME] = "kernel", 24 | [UDEBUG_META_IFACE_DESC] = "tracepoint events", 25 | }, 26 | }; 27 | static struct udebug_remote_buf trace_rbuf = { 28 | .node.key = (void *)(uintptr_t)TRACE_RING_ID, 29 | .buf.hdr = &trace_hdr, 30 | .meta = &trace_meta, 31 | .pcap_iface = ~0 32 | }; 33 | 34 | struct uc_pcap { 35 | struct pcap_context pcap; 36 | FILE *f; 37 | }; 38 | 39 | struct uc_remote_ring { 40 | struct udebug_remote_buf rb; 41 | struct udebug_packet_info info; 42 | uc_vm_t *vm; 43 | }; 44 | 45 | struct uc_trace_ring { 46 | uc_vm_t *vm; 47 | uc_value_t *res; 48 | struct uloop_fd fd; 49 | int dir_fd; 50 | 51 | unsigned int ts_ofs; 52 | char *instance; 53 | }; 54 | 55 | static void 56 | uc_udebug_call_cb(uc_vm_t *vm, uc_value_t *this) 57 | { 58 | uc_value_t *cb; 59 | 60 | if (!this) 61 | return; 62 | 63 | cb = ucv_resource_value_get(this, 0); 64 | if (!ucv_is_callable(cb)) 65 | return; 66 | 67 | uc_vm_stack_push(vm, ucv_get(this)); 68 | uc_vm_stack_push(vm, ucv_get(cb)); 69 | if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE) 70 | return; 71 | 72 | ucv_put(uc_vm_stack_pop(vm)); 73 | } 74 | 75 | static void 76 | uc_udebug_notify_cb(struct udebug *ctx, struct udebug_remote_buf *rb) 77 | { 78 | struct uc_remote_ring *rr = container_of(rb, struct uc_remote_ring, rb); 79 | 80 | uc_udebug_call_cb(rr->vm, rb->priv); 81 | } 82 | 83 | static void 84 | __uc_udebug_init(void) 85 | { 86 | if (init_done) 87 | return; 88 | 89 | udebug_init(&u); 90 | avl_insert(&u.remote_rings, &trace_rbuf.node); 91 | u.notify_cb = uc_udebug_notify_cb; 92 | init_done = true; 93 | } 94 | 95 | static uc_value_t * 96 | uc_udebug_init(uc_vm_t *vm, size_t nargs) 97 | { 98 | uc_value_t *arg = uc_fn_arg(0); 99 | uc_value_t *flag_auto = uc_fn_arg(1); 100 | const char *path = NULL; 101 | 102 | if (ucv_type(arg) == UC_STRING) 103 | path = ucv_string_get(arg); 104 | 105 | if (init_done && !path && udebug_is_connected(&u)) 106 | goto out; 107 | 108 | __uc_udebug_init(); 109 | if (flag_auto && !ucv_is_truish(flag_auto)) { 110 | if (udebug_connect(&u, path)) 111 | return NULL; 112 | } else { 113 | udebug_auto_connect(&u, path); 114 | } 115 | 116 | out: 117 | return ucv_boolean_new(true); 118 | } 119 | 120 | static struct udebug_remote_buf * 121 | uc_fn_rbuf(uc_vm_t *vm) 122 | { 123 | struct udebug_remote_buf *rb = uc_fn_thisval("udebug.rbuf"); 124 | 125 | if (!rb || !rb->buf.data) 126 | return NULL; 127 | 128 | return rb; 129 | } 130 | 131 | static uc_value_t * 132 | uc_udebug_get_ring(uc_vm_t *vm, size_t nargs) 133 | { 134 | struct uc_remote_ring *rr; 135 | struct udebug_remote_buf *rb; 136 | struct udebug_packet_info *info; 137 | uc_value_t *arg = uc_fn_arg(0); 138 | uc_value_t *id, *proc, *name, *pid; 139 | size_t ifname_len, ifdesc_len, len; 140 | char *buf; 141 | uc_value_t *res; 142 | 143 | #define R_IFACE_DESC "%s:%d" 144 | 145 | if (ucv_type(arg) != UC_OBJECT) 146 | return NULL; 147 | 148 | id = ucv_object_get(arg, "id", NULL); 149 | proc = ucv_object_get(arg, "proc_name", NULL); 150 | name = ucv_object_get(arg, "ring_name", NULL); 151 | pid = ucv_object_get(arg, "pid", NULL); 152 | 153 | if (ucv_type(id) != UC_INTEGER || 154 | ucv_type(proc) != UC_STRING || 155 | ucv_type(name) != UC_STRING || 156 | ucv_type(pid) != UC_INTEGER) 157 | return NULL; 158 | 159 | ifname_len = strlen(ucv_string_get(name)) + 1; 160 | ifdesc_len = sizeof(R_IFACE_DESC) + strlen(ucv_string_get(proc)) + 10; 161 | len = ifname_len + ifdesc_len + sizeof(*rr); 162 | res = ucv_resource_create_ex(vm, "udebug.rbuf", (void **)&rr, 1, len); 163 | if (!res) 164 | return NULL; 165 | 166 | rr->vm = vm; 167 | rb = &rr->rb; 168 | info = &rr->info; 169 | rb->meta = info; 170 | rb->priv = res; 171 | buf = (char *)(rr + 1); 172 | 173 | strcpy(buf, ucv_string_get(name)); 174 | info->attr[UDEBUG_META_IFACE_NAME] = buf; 175 | buf += ifname_len; 176 | 177 | snprintf(buf, ifdesc_len, R_IFACE_DESC, 178 | ucv_string_get(proc), (unsigned int)ucv_int64_get(pid)); 179 | info->attr[UDEBUG_META_IFACE_DESC] = buf; 180 | 181 | if (udebug_remote_buf_map(&u, rb, (uint32_t)ucv_int64_get(id))) { 182 | ucv_put(res); 183 | return NULL; 184 | } 185 | 186 | return res; 187 | } 188 | 189 | static uc_value_t * 190 | uc_udebug_rbuf_fetch(uc_vm_t *vm, size_t nargs) 191 | { 192 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 193 | struct udebug_snapshot *s; 194 | 195 | if (!rb) 196 | return NULL; 197 | 198 | s = udebug_remote_buf_snapshot(rb); 199 | if (!s) 200 | return NULL; 201 | 202 | return ucv_resource_create(vm, "udebug.snapshot", s); 203 | } 204 | 205 | static uc_value_t * 206 | uc_udebug_rbuf_set_poll_cb(uc_vm_t *vm, size_t nargs) 207 | { 208 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 209 | uc_value_t *res = _uc_fn_this_res(vm); 210 | uc_value_t *val = uc_fn_arg(0); 211 | 212 | if (!rb) 213 | return NULL; 214 | 215 | if (val && !ucv_is_callable(val)) 216 | return NULL; 217 | 218 | ucv_resource_value_set(res, 0, ucv_get(val)); 219 | 220 | if (rb->poll == !!val) 221 | goto out; 222 | 223 | if (!u.fd.registered) 224 | udebug_add_uloop(&u); 225 | udebug_remote_buf_set_poll(&u, rb, !!val); 226 | ucv_resource_persistent_set(res, !!val); 227 | if (val) 228 | ucv_get(rb->priv); 229 | else 230 | ucv_put(rb->priv); 231 | 232 | out: 233 | return ucv_boolean_new(true); 234 | } 235 | 236 | static uc_value_t * 237 | uc_udebug_rbuf_set_fetch_duration(uc_vm_t *vm, size_t nargs) 238 | { 239 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 240 | uc_value_t *val = uc_fn_arg(0); 241 | uint64_t ts; 242 | double t; 243 | 244 | if (!rb) 245 | return NULL; 246 | 247 | t = ucv_double_get(val); 248 | if (isnan(t)) 249 | return NULL; 250 | 251 | ts = udebug_timestamp(); 252 | ts -= (uint64_t)(fabs(t) * UDEBUG_TS_SEC); 253 | udebug_remote_buf_set_start_time(rb, ts); 254 | 255 | return ucv_boolean_new(true); 256 | } 257 | 258 | static uc_value_t * 259 | uc_udebug_rbuf_set_fetch_count(uc_vm_t *vm, size_t nargs) 260 | { 261 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 262 | uc_value_t *val = uc_fn_arg(0); 263 | uint32_t count; 264 | 265 | if (!rb) 266 | return NULL; 267 | 268 | count = ucv_int64_get(val); 269 | udebug_remote_buf_set_start_offset(rb, count); 270 | 271 | return ucv_boolean_new(true); 272 | } 273 | 274 | static uc_value_t * 275 | uc_udebug_rbuf_change_flags(uc_vm_t *vm, size_t nargs) 276 | { 277 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 278 | uc_value_t *mask = uc_fn_arg(0); 279 | uc_value_t *set = uc_fn_arg(1); 280 | 281 | if (!rb) 282 | return NULL; 283 | 284 | if (ucv_type(mask) != UC_INTEGER || ucv_type(set) != UC_INTEGER) 285 | return NULL; 286 | 287 | udebug_remote_buf_set_flags(rb, ucv_int64_get(mask), ucv_int64_get(set)); 288 | return ucv_boolean_new(true); 289 | } 290 | 291 | static uc_value_t * 292 | uc_udebug_rbuf_get_flags(uc_vm_t *vm, size_t nargs) 293 | { 294 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 295 | 296 | if (!rb) 297 | return NULL; 298 | 299 | return ucv_int64_new(udebug_buf_flags(&rb->buf)); 300 | } 301 | 302 | static uc_value_t * 303 | uc_udebug_rbuf_get_info(uc_vm_t *vm, size_t nargs) 304 | { 305 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 306 | uc_value_t *val; 307 | 308 | if (!rb) 309 | return NULL; 310 | 311 | val = ucv_object_new(vm); 312 | ucv_object_add(val, "ring_size", ucv_int64_new(rb->buf.hdr->ring_size)); 313 | ucv_object_add(val, "data_size", ucv_int64_new(rb->buf.hdr->data_size)); 314 | ucv_object_add(val, "format", ucv_int64_new(rb->buf.hdr->format)); 315 | ucv_object_add(val, "sub_format", ucv_int64_new(rb->buf.hdr->sub_format)); 316 | 317 | return val; 318 | } 319 | 320 | static uc_value_t * 321 | uc_udebug_rbuf_close(uc_vm_t *vm, size_t nargs) 322 | { 323 | struct udebug_remote_buf *rb = uc_fn_rbuf(vm); 324 | 325 | if (rb) 326 | udebug_remote_buf_unmap(&u, rb); 327 | 328 | return NULL; 329 | } 330 | 331 | static void 332 | uc_udebug_pcap_init(struct uc_pcap *p, uc_value_t *args) 333 | { 334 | uc_value_t *hw, *os, *app; 335 | struct pcap_meta meta = {}; 336 | 337 | if (ucv_type(args) == UC_OBJECT) { 338 | hw = ucv_object_get(args, "hw", NULL); 339 | os = ucv_object_get(args, "os", NULL); 340 | app = ucv_object_get(args, "app", NULL); 341 | 342 | meta.hw = ucv_string_get(hw); 343 | meta.os = ucv_string_get(os); 344 | meta.app = ucv_string_get(app); 345 | } 346 | 347 | pcap_init(&p->pcap, &meta); 348 | } 349 | 350 | static uc_value_t * 351 | uc_debug_pcap_init(uc_vm_t *vm, int fd, uc_value_t *args) 352 | { 353 | struct uc_pcap *p; 354 | FILE *f; 355 | 356 | if (fd < 0) 357 | return NULL; 358 | 359 | f = fdopen(fd, "w"); 360 | if (!f) 361 | return NULL; 362 | 363 | p = calloc(1, sizeof(*p)); 364 | p->f = f; 365 | uc_udebug_pcap_init(p, args); 366 | pcap_block_write_file(p->f); 367 | 368 | return ucv_resource_create(vm, "udebug.pcap", p); 369 | } 370 | 371 | static uc_value_t * 372 | uc_udebug_pcap_file(uc_vm_t *vm, size_t nargs) 373 | { 374 | uc_value_t *file = uc_fn_arg(0); 375 | uc_value_t *args = uc_fn_arg(1); 376 | int fd = -1; 377 | 378 | if (ucv_type(file) == UC_STRING) { 379 | fd = open(ucv_string_get(file), O_WRONLY | O_CREAT, 0644); 380 | if (ftruncate(fd, 0) < 0) { 381 | close(fd); 382 | return NULL; 383 | } 384 | } else if (!file) 385 | fd = STDOUT_FILENO; 386 | 387 | return uc_debug_pcap_init(vm, fd, args); 388 | } 389 | 390 | static uc_value_t * 391 | uc_udebug_pcap_udp(uc_vm_t *vm, size_t nargs) 392 | { 393 | uc_value_t *host = uc_fn_arg(0); 394 | uc_value_t *port = uc_fn_arg(1); 395 | uc_value_t *args = uc_fn_arg(2); 396 | const char *port_str; 397 | int fd = -1; 398 | 399 | if (ucv_type(host) != UC_STRING) 400 | return NULL; 401 | 402 | if (ucv_type(port) == UC_STRING) 403 | port_str = ucv_string_get(port); 404 | else if (ucv_type(port) == UC_INTEGER) 405 | port_str = usock_port(ucv_int64_get(port)); 406 | else 407 | return NULL; 408 | 409 | fd = usock(USOCK_UDP, ucv_string_get(host), port_str); 410 | 411 | return uc_debug_pcap_init(vm, fd, args); 412 | } 413 | 414 | static struct udebug_snapshot * 415 | uc_get_snapshot(uc_value_t *val) 416 | { 417 | return ucv_resource_data(val, "udebug.snapshot"); 418 | } 419 | 420 | static uc_value_t * 421 | uc_udebug_pcap_write(uc_vm_t *vm, size_t nargs) 422 | { 423 | struct uc_pcap *p = uc_fn_thisval("udebug.pcap"); 424 | uc_value_t *arg = uc_fn_arg(0); 425 | size_t n = ucv_type(arg) == UC_ARRAY ? ucv_array_length(arg) : 1; 426 | struct udebug_snapshot **s; 427 | struct udebug_iter it; 428 | bool ret = false; 429 | 430 | if (!p) 431 | return NULL; 432 | 433 | s = alloca(n * sizeof(*s)); 434 | if (ucv_type(arg) == UC_ARRAY) 435 | for (size_t i = 0; i < n; i++) { 436 | if ((s[i] = uc_get_snapshot(ucv_array_get(arg, i))) == NULL) 437 | goto out; 438 | } else { 439 | if ((s[0] = uc_get_snapshot(arg)) == NULL) 440 | goto out; 441 | } 442 | 443 | udebug_iter_start(&it, s, n); 444 | while (udebug_iter_next(&it)) { 445 | struct udebug_remote_buf *rb; 446 | 447 | rb = udebug_remote_buf_get(&u, it.s->rbuf_idx); 448 | if (!pcap_interface_is_valid(&p->pcap, rb->pcap_iface)) { 449 | if (pcap_interface_rbuf_init(&p->pcap, rb)) 450 | continue; 451 | 452 | if (!pcap_block_write_file(p->f)) 453 | return NULL; 454 | } 455 | 456 | if (pcap_snapshot_packet_init(&u, &it)) 457 | continue; 458 | 459 | if (!pcap_block_write_file(p->f)) 460 | return NULL; 461 | 462 | ret = true; 463 | } 464 | 465 | if (ferror(p->f)) 466 | return NULL; 467 | 468 | out: 469 | return ucv_boolean_new(ret); 470 | } 471 | 472 | static void 473 | uc_udebug_pcap_free(void *ptr) 474 | { 475 | struct uc_pcap *p = ptr; 476 | 477 | if (!p) 478 | return; 479 | 480 | if (p->f) 481 | fclose(p->f); 482 | free(p); 483 | } 484 | 485 | static uc_value_t * 486 | uc_udebug_pcap_close(uc_vm_t *vm, size_t nargs) 487 | { 488 | void **p = uc_fn_this("udebug.pcap"); 489 | 490 | if (!p) 491 | return NULL; 492 | 493 | uc_udebug_pcap_free(*p); 494 | *p = NULL; 495 | 496 | return NULL; 497 | } 498 | 499 | static uc_value_t * 500 | uc_udebug_snapshot_get_ring(uc_vm_t *vm, size_t nargs) 501 | { 502 | struct udebug_snapshot *s = uc_fn_thisval("udebug.snapshot"); 503 | struct udebug_remote_buf *rb; 504 | 505 | if (!s) 506 | return NULL; 507 | 508 | rb = udebug_remote_buf_get(&u, s->rbuf_idx); 509 | if (!rb) 510 | return NULL; 511 | 512 | return ucv_get(rb->priv); 513 | } 514 | 515 | static uc_value_t * 516 | uc_udebug_snapshot_entries(uc_vm_t *vm, size_t nargs) 517 | { 518 | struct udebug_snapshot *s = uc_fn_thisval("udebug.snapshot"); 519 | 520 | if (!s) 521 | return NULL; 522 | 523 | return ucv_int64_new(s->n_entries); 524 | } 525 | 526 | static uc_value_t * 527 | uc_udebug_foreach_packet(uc_vm_t *vm, size_t nargs) 528 | { 529 | uc_value_t *arg = uc_fn_arg(0); 530 | uc_value_t *fn = uc_fn_arg(1); 531 | size_t n = ucv_type(arg) == UC_ARRAY ? ucv_array_length(arg) : 1; 532 | struct udebug_snapshot **s; 533 | struct udebug_iter it; 534 | 535 | if (!ucv_is_callable(fn)) 536 | return NULL; 537 | 538 | s = alloca(n * sizeof(*s)); 539 | if (ucv_type(arg) == UC_ARRAY) 540 | for (size_t i = 0; i < n; i++) { 541 | if ((s[i] = uc_get_snapshot(ucv_array_get(arg, i))) == NULL) 542 | return NULL; 543 | } else { 544 | if ((s[0] = uc_get_snapshot(arg)) == NULL) 545 | return NULL; 546 | } 547 | 548 | udebug_iter_start(&it, s, n); 549 | while (udebug_iter_next(&it)) { 550 | uc_value_t *s_obj; 551 | 552 | if (ucv_type(arg) == UC_ARRAY) 553 | s_obj = ucv_array_get(arg, it.s_idx); 554 | else 555 | s_obj = arg; 556 | 557 | uc_vm_stack_push(vm, ucv_get(_uc_fn_this_res(vm))); 558 | uc_vm_stack_push(vm, ucv_get(fn)); 559 | uc_vm_stack_push(vm, ucv_get(s_obj)); 560 | uc_vm_stack_push(vm, ucv_string_new_length(it.data, it.len)); 561 | uc_vm_stack_push(vm, ucv_int64_new(it.timestamp)); 562 | 563 | if (uc_vm_call(vm, true, 3) != EXCEPTION_NONE) 564 | break; 565 | 566 | ucv_put(uc_vm_stack_pop(vm)); 567 | } 568 | 569 | return NULL; 570 | } 571 | 572 | static uc_value_t * 573 | uc_udebug_create_ring(uc_vm_t *vm, size_t nargs) 574 | { 575 | uc_value_t *name, *flags_arr, *size, *entries, *format; 576 | uc_value_t *meta_obj = uc_fn_arg(0); 577 | struct udebug_buf_flag *flags; 578 | struct udebug_buf_meta *meta; 579 | struct udebug_buf *buf; 580 | size_t flag_str_len = 0; 581 | size_t flags_len = 0; 582 | char *name_buf, *flag_name_buf; 583 | 584 | if (ucv_type(meta_obj) != UC_OBJECT) 585 | return NULL; 586 | 587 | name = ucv_object_get(meta_obj, "name", NULL); 588 | flags_arr = ucv_object_get(meta_obj, "flags", NULL); 589 | size = ucv_object_get(meta_obj, "size", NULL); 590 | entries = ucv_object_get(meta_obj, "entries", NULL); 591 | 592 | if (ucv_type(name) != UC_STRING || 593 | ucv_type(size) != UC_INTEGER || ucv_type(entries) != UC_INTEGER) 594 | return NULL; 595 | 596 | if (ucv_type(flags_arr) == UC_ARRAY) { 597 | flags_len = ucv_array_length(flags_arr); 598 | for (size_t i = 0; i < flags_len; i++) { 599 | uc_value_t *f = ucv_array_get(flags_arr, i); 600 | if (ucv_type(f) != UC_STRING) 601 | return NULL; 602 | flag_str_len += strlen(ucv_string_get(f)) + 1; 603 | } 604 | } 605 | 606 | buf = calloc_a(sizeof(*buf), 607 | &name_buf, strlen(ucv_string_get(name)) + 1, 608 | &meta, sizeof(*meta), 609 | &flags, flags_len * sizeof(*flags), 610 | &flag_name_buf, flag_str_len); 611 | meta->name = strcpy(name_buf, ucv_string_get(name)); 612 | meta->format = UDEBUG_FORMAT_STRING; 613 | meta->flags = flags; 614 | 615 | format = ucv_object_get(meta_obj, "format", NULL); 616 | if (ucv_type(format) == UC_INTEGER) { 617 | meta->format = UDEBUG_FORMAT_PACKET; 618 | meta->sub_format = ucv_int64_get(format); 619 | } 620 | 621 | for (size_t i = 0; i < flags_len; i++) { 622 | uc_value_t *f = ucv_array_get(flags_arr, i); 623 | const char *str = ucv_string_get(f); 624 | size_t len = strlen(str) + 1; 625 | 626 | flags->name = memcpy(name_buf, str, len); 627 | flags->mask = 1ULL << i; 628 | name_buf += len; 629 | meta->n_flags++; 630 | } 631 | 632 | if (udebug_buf_init(buf, ucv_int64_get(entries), ucv_int64_get(size))) { 633 | free(buf); 634 | return NULL; 635 | } 636 | 637 | udebug_buf_add(&u, buf, meta); 638 | 639 | return ucv_resource_create(vm, "udebug.wbuf", buf); 640 | } 641 | 642 | static void wbuf_free(void *ptr) 643 | { 644 | if (!ptr) 645 | return; 646 | 647 | udebug_buf_free(ptr); 648 | } 649 | 650 | static uc_value_t * 651 | uc_udebug_wbuf_flags(uc_vm_t *vm, size_t nargs) 652 | { 653 | struct udebug_buf *buf = uc_fn_thisval("udebug.wbuf"); 654 | 655 | if (!buf) 656 | return NULL; 657 | 658 | return ucv_int64_new(udebug_buf_flags(buf)); 659 | } 660 | 661 | static uc_value_t * 662 | uc_udebug_wbuf_close(uc_vm_t *vm, size_t nargs) 663 | { 664 | void **p = uc_fn_this("udebug.wbuf"); 665 | 666 | if (!p) 667 | return NULL; 668 | 669 | wbuf_free(*p); 670 | *p = NULL; 671 | 672 | return NULL; 673 | } 674 | 675 | static void 676 | uc_udebug_wbuf_append(uc_vm_t *vm, struct udebug_buf *buf, uc_value_t *val) 677 | { 678 | struct printbuf *pb; 679 | size_t len; 680 | 681 | if (!val) 682 | return; 683 | 684 | switch (ucv_type(val)) { 685 | case UC_STRING: 686 | udebug_entry_append(buf, ucv_string_get(val), ucv_string_length(val)); 687 | break; 688 | case UC_ARRAY: 689 | len = ucv_array_length(val); 690 | for (size_t i = 0; i < len; i++) 691 | uc_udebug_wbuf_append(vm, buf, ucv_array_get(val, i)); 692 | break; 693 | default: 694 | pb = xprintbuf_new(); 695 | ucv_to_stringbuf(vm, pb, val, false); 696 | udebug_entry_append(buf, pb->buf, pb->bpos); 697 | printbuf_free(pb); 698 | break; 699 | } 700 | } 701 | 702 | static uc_value_t * 703 | uc_udebug_wbuf_add(uc_vm_t *vm, size_t nargs) 704 | { 705 | struct udebug_buf *buf = uc_fn_thisval("udebug.wbuf"); 706 | uc_value_t *arg = uc_fn_arg(0); 707 | 708 | if (!buf || !arg) 709 | return NULL; 710 | 711 | udebug_entry_init(buf); 712 | uc_udebug_wbuf_append(vm, buf, arg); 713 | udebug_entry_add(buf); 714 | 715 | return ucv_boolean_new(true); 716 | } 717 | 718 | static void 719 | uc_udebug_trace_uloop_cb(struct uloop_fd *fd, unsigned int events) 720 | { 721 | struct uc_trace_ring *tr = container_of(fd, struct uc_trace_ring, fd); 722 | 723 | uc_udebug_call_cb(tr->vm, tr->res); 724 | } 725 | 726 | static bool 727 | trace_parse_meta(struct uc_trace_ring *tr, int fd) 728 | { 729 | FILE *f = fdopen(fd, "r"); 730 | char str[256]; 731 | char *cur; 732 | 733 | if (!f) { 734 | close(fd); 735 | return false; 736 | } 737 | 738 | while (fgets(str, sizeof(str), f)) { 739 | cur = strstr(str, " TIMESTAMP "); 740 | if (!cur) 741 | continue; 742 | 743 | while (cur[-1] == ' ' && cur[-2] != '|') 744 | cur--; 745 | 746 | tr->ts_ofs = cur - str; 747 | break; 748 | } 749 | 750 | fclose(f); 751 | 752 | return tr->ts_ofs != 0; 753 | } 754 | 755 | static uc_value_t * 756 | uc_udebug_trace_ring(uc_vm_t *vm, size_t nargs) 757 | { 758 | uc_value_t *instance_arg = uc_fn_arg(0); 759 | const char *instance = "udebug"; 760 | struct uc_trace_ring *tr; 761 | int dfd, fd, ret; 762 | uc_value_t *res; 763 | 764 | if (instance_arg) { 765 | if (ucv_type(instance_arg) != UC_STRING) 766 | return NULL; 767 | 768 | instance = ucv_string_get(instance_arg); 769 | } 770 | 771 | if (trace_dirfd < 0) 772 | trace_dirfd = open("/sys/kernel/debug/tracing/instances", O_RDONLY | O_DIRECTORY); 773 | if (trace_dirfd < 0) 774 | return NULL; 775 | 776 | ret = unlinkat(trace_dirfd, instance, AT_REMOVEDIR); 777 | ret = mkdirat(trace_dirfd, instance, 0700); 778 | if (ret < 0) 779 | return NULL; 780 | 781 | dfd = openat(trace_dirfd, instance, O_RDONLY | O_DIRECTORY); 782 | if (dfd < 0) 783 | return NULL; 784 | 785 | fd = openat(dfd, "trace_pipe", O_RDONLY); 786 | if (fd < 0) 787 | goto error; 788 | 789 | res = ucv_resource_create_ex(vm, "udebug.tbuf", (void **)&tr, 1, sizeof(*tr) + strlen(instance + 1)); 790 | if (!res) 791 | goto error; 792 | 793 | __uc_udebug_init(); 794 | tr->vm = vm; 795 | tr->dir_fd = dfd; 796 | tr->fd.fd = fd; 797 | tr->fd.cb = uc_udebug_trace_uloop_cb; 798 | tr->instance = (char *)(tr + 1); 799 | strcpy(tr->instance, instance); 800 | 801 | fd = openat(dfd, "trace", O_RDONLY); 802 | if (fd < 0 || !trace_parse_meta(tr, fd)) { 803 | ucv_put(res); 804 | return NULL; 805 | } 806 | 807 | return res; 808 | 809 | error: 810 | close(dfd); 811 | return NULL; 812 | } 813 | 814 | static void tbuf_close(void *ptr) 815 | { 816 | struct uc_trace_ring *tr = ptr; 817 | 818 | if (tr->fd.fd < 0) 819 | return; 820 | 821 | uloop_fd_delete(&tr->fd); 822 | close(tr->fd.fd); 823 | close(tr->dir_fd); 824 | tr->fd.fd = -1; 825 | unlinkat(trace_dirfd, tr->instance, AT_REMOVEDIR); 826 | } 827 | 828 | static struct uc_trace_ring * 829 | uc_fn_tbuf(uc_vm_t *vm) 830 | { 831 | struct uc_trace_ring *tr = uc_fn_thisval("udebug.tbuf"); 832 | 833 | if (!tr || tr->fd.fd < 0) 834 | return NULL; 835 | 836 | return tr; 837 | } 838 | 839 | static uc_value_t * 840 | uc_udebug_tbuf_set_file(uc_vm_t *vm, size_t nargs) 841 | { 842 | struct uc_trace_ring *tr = uc_fn_tbuf(vm); 843 | uc_value_t *file = uc_fn_arg(0); 844 | uc_value_t *val = uc_fn_arg(1); 845 | uc_value_t *ret = NULL; 846 | FILE *f; 847 | int fd; 848 | 849 | if (!tr || ucv_type(file) != UC_STRING || ucv_type(val) != UC_STRING) 850 | return NULL; 851 | 852 | fd = openat(tr->dir_fd, ucv_string_get(file), O_WRONLY); 853 | if (fd < 0) 854 | return NULL; 855 | 856 | f = fdopen(fd, "w"); 857 | if (!f) { 858 | close(fd); 859 | return NULL; 860 | } 861 | 862 | if (fwrite(ucv_string_get(val), ucv_string_length(val), 1, f) == 1) 863 | ret = ucv_boolean_new(true); 864 | 865 | fclose(f); 866 | 867 | return ret; 868 | } 869 | 870 | 871 | static uc_value_t * 872 | uc_udebug_tbuf_set_poll_cb(uc_vm_t *vm, size_t nargs) 873 | { 874 | struct uc_trace_ring *tr = uc_fn_tbuf(vm); 875 | uc_value_t *res = _uc_fn_this_res(vm); 876 | uc_value_t *val = uc_fn_arg(0); 877 | 878 | if (!tr) 879 | return NULL; 880 | 881 | if (val && !ucv_is_callable(val)) 882 | return NULL; 883 | 884 | ucv_resource_value_set(res, 0, ucv_get(val)); 885 | 886 | if (!!tr->res == !!val) 887 | goto out; 888 | 889 | ucv_resource_persistent_set(res, !!val); 890 | if (val) { 891 | uloop_fd_add(&tr->fd, ULOOP_READ); 892 | tr->res = ucv_get(res); 893 | } else { 894 | uloop_fd_delete(&tr->fd); 895 | ucv_put(tr->res); 896 | tr->res = NULL; 897 | } 898 | 899 | out: 900 | return ucv_boolean_new(true); 901 | } 902 | 903 | static struct udebug_snapshot * 904 | snapshot_realloc(struct udebug_snapshot *s, size_t data_size) 905 | { 906 | s = xrealloc(s, sizeof(*s) + data_size); 907 | s->data = s + 1; 908 | return s; 909 | } 910 | 911 | static bool 912 | trace_parse_timestamp(struct udebug_ptr *ptr, char *str) 913 | { 914 | uint64_t ts, usec; 915 | char *err; 916 | 917 | ts = strtoul(str, &err, 10); 918 | if (*err != '.') 919 | return false; 920 | 921 | usec = strtoul(err + 1, &err, 10); 922 | if (*err != ':') 923 | return false; 924 | 925 | ptr->timestamp = ts * 1000000 + usec; 926 | memmove(str, err + 1, strlen(err + 1) + 1); 927 | 928 | return true; 929 | } 930 | 931 | static uc_value_t * 932 | uc_udebug_tbuf_fetch(uc_vm_t *vm, size_t nargs) 933 | { 934 | struct uc_trace_ring *tr = uc_fn_tbuf(vm); 935 | struct udebug_snapshot *s = NULL; 936 | struct udebug_ptr *ptr = NULL; 937 | size_t data_ofs = 0; 938 | size_t data_size = 0; 939 | size_t ptr_ofs = 0; 940 | size_t ptr_size = 0; 941 | char *cur, *next; 942 | size_t ofs = 0; 943 | char buf[8192]; 944 | ssize_t len; 945 | 946 | if (!tr) 947 | return NULL; 948 | 949 | read_again: 950 | len = read(tr->fd.fd, buf + ofs, sizeof(buf) - ofs); 951 | if (len < 0) { 952 | if (errno == EINTR) 953 | goto read_again; 954 | 955 | if (errno == EAGAIN || data_size) 956 | goto done; 957 | 958 | goto error; 959 | } 960 | len += ofs; 961 | 962 | cur = buf; 963 | while ((next = memchr(cur, '\n', len)) != NULL) { 964 | struct udebug_ptr *cur_ptr; 965 | char *line = cur; 966 | 967 | *next = 0; 968 | 969 | while (isspace(*line)) 970 | line++; 971 | 972 | if (*line == '#' || !line[0]) 973 | goto next_line; 974 | 975 | if (next - cur <= tr->ts_ofs) 976 | goto next_line; 977 | 978 | if (ptr_ofs >= ptr_size) { 979 | if (!ptr_size) 980 | ptr_size = 4; 981 | else 982 | ptr_size *= 2; 983 | 984 | ptr = xrealloc(ptr, ptr_size * sizeof(*ptr)); 985 | } 986 | 987 | cur_ptr = &ptr[ptr_ofs]; 988 | if (!trace_parse_timestamp(cur_ptr, cur + tr->ts_ofs)) 989 | goto next_line; 990 | 991 | cur_ptr->start = data_ofs; 992 | cur_ptr->len = strlen(line); 993 | data_ofs += cur_ptr->len + 1; 994 | if (data_ofs >= data_size) { 995 | if (!data_size) 996 | data_size = 256; 997 | else 998 | data_size *= 2; 999 | s = snapshot_realloc(s, data_size); 1000 | } 1001 | strcpy(s->data + cur_ptr->start, line); 1002 | ptr_ofs++; 1003 | 1004 | next_line: 1005 | len -= next + 1 - cur; 1006 | cur = next + 1; 1007 | } 1008 | 1009 | if (len) 1010 | memmove(buf, cur, len); 1011 | ofs = len; 1012 | goto read_again; 1013 | 1014 | done: 1015 | if (!s) 1016 | goto error; 1017 | 1018 | ptr_size = ptr_ofs * sizeof(*ptr); 1019 | data_ofs = (data_ofs + 3) & ~3; 1020 | data_size = data_ofs + ptr_size; 1021 | s = snapshot_realloc(s, data_size); 1022 | s->data_size = data_ofs; 1023 | s->entries = memcpy(s->data + data_ofs, ptr, ptr_size); 1024 | s->n_entries = ptr_ofs; 1025 | s->rbuf_idx = TRACE_RING_ID; 1026 | free(ptr); 1027 | 1028 | return ucv_resource_create(vm, "udebug.snapshot", s); 1029 | 1030 | error: 1031 | free(ptr); 1032 | free(s); 1033 | return NULL; 1034 | } 1035 | 1036 | static uc_value_t * 1037 | uc_udebug_tbuf_close(uc_vm_t *vm, size_t nargs) 1038 | { 1039 | struct uc_trace_ring *tr = uc_fn_tbuf(vm); 1040 | 1041 | if (!tr) 1042 | return NULL; 1043 | 1044 | tbuf_close(tr); 1045 | 1046 | if (!tr->res) 1047 | return NULL; 1048 | 1049 | ucv_resource_persistent_set(tr->res, false); 1050 | ucv_put(tr->res); 1051 | tr->res = NULL; 1052 | 1053 | return NULL; 1054 | } 1055 | 1056 | 1057 | static const uc_function_list_t pcap_fns[] = { 1058 | { "close", uc_udebug_pcap_close }, 1059 | { "write", uc_udebug_pcap_write }, 1060 | }; 1061 | 1062 | static const uc_function_list_t snapshot_fns[] = { 1063 | { "get_ring", uc_udebug_snapshot_get_ring }, 1064 | { "entries", uc_udebug_snapshot_entries } 1065 | }; 1066 | 1067 | static const uc_function_list_t wbuf_fns[] = { 1068 | { "add", uc_udebug_wbuf_add }, 1069 | { "flags", uc_udebug_wbuf_flags }, 1070 | { "close", uc_udebug_wbuf_close }, 1071 | }; 1072 | 1073 | static const uc_function_list_t tbuf_fns[] = { 1074 | { "set_poll_cb", uc_udebug_tbuf_set_poll_cb }, 1075 | { "set_file", uc_udebug_tbuf_set_file }, 1076 | { "fetch", uc_udebug_tbuf_fetch }, 1077 | { "close", uc_udebug_tbuf_close }, 1078 | }; 1079 | 1080 | static const uc_function_list_t rbuf_fns[] = { 1081 | { "set_poll_cb", uc_udebug_rbuf_set_poll_cb }, 1082 | { "fetch", uc_udebug_rbuf_fetch }, 1083 | { "change_flags", uc_udebug_rbuf_change_flags }, 1084 | { "get_flags", uc_udebug_rbuf_get_flags }, 1085 | { "set_fetch_duration", uc_udebug_rbuf_set_fetch_duration }, 1086 | { "set_fetch_count", uc_udebug_rbuf_set_fetch_count }, 1087 | { "get_info", uc_udebug_rbuf_get_info }, 1088 | { "close", uc_udebug_rbuf_close }, 1089 | }; 1090 | 1091 | static const uc_function_list_t global_fns[] = { 1092 | { "init", uc_udebug_init }, 1093 | { "create_ring", uc_udebug_create_ring }, 1094 | { "get_ring", uc_udebug_get_ring }, 1095 | { "trace_ring", uc_udebug_trace_ring }, 1096 | { "pcap_file", uc_udebug_pcap_file }, 1097 | { "pcap_udp", uc_udebug_pcap_udp }, 1098 | { "foreach_packet", uc_udebug_foreach_packet }, 1099 | }; 1100 | 1101 | static void rbuf_free(void *ptr) 1102 | { 1103 | struct udebug_remote_buf *rb = ptr; 1104 | 1105 | if (rb->buf.data) 1106 | udebug_remote_buf_unmap(&u, rb); 1107 | } 1108 | 1109 | void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 1110 | { 1111 | uc_function_list_register(scope, global_fns); 1112 | 1113 | #define ADD_CONST(name) \ 1114 | ucv_object_add(scope, #name, ucv_int64_new(UDEBUG_##name)) 1115 | ADD_CONST(FORMAT_PACKET); 1116 | ADD_CONST(FORMAT_STRING); 1117 | ADD_CONST(FORMAT_BLOBMSG); 1118 | ADD_CONST(DLT_ETHERNET); 1119 | ADD_CONST(DLT_PPP); 1120 | ADD_CONST(DLT_IEEE_802_11); 1121 | ADD_CONST(DLT_IEEE_802_11_RADIOTAP); 1122 | ADD_CONST(DLT_NETLINK); 1123 | 1124 | uc_type_declare(vm, "udebug.wbuf", wbuf_fns, wbuf_free); 1125 | uc_type_declare(vm, "udebug.rbuf", rbuf_fns, rbuf_free); 1126 | uc_type_declare(vm, "udebug.tbuf", tbuf_fns, tbuf_close); 1127 | uc_type_declare(vm, "udebug.snapshot", snapshot_fns, free); 1128 | uc_type_declare(vm, "udebug.pcap", pcap_fns, uc_udebug_pcap_free); 1129 | } 1130 | --------------------------------------------------------------------------------