目录 [隐藏] 1. 名称2. 语法3. 选项4. 描述4.1. QDISCS4.2. CLASSES4.3. FILTERS4.4. QEVENTS4.5. 无类 QDISCS4.6. 配置无类 QDISCS4.7. 类别 QDISCS4.8. 操作原理4.9. 命名4.10. 参数5. TC 命令5.1. 监控5.2. 选项5.3. 格式5.4. 示例6. 历史7. 参见8. 作者 TC1. 名称tc - 显示/操作流量控制（traffic control）设置2. 语法tc [ OPTIONS ] qdisc [ add | change | replace | link | delete ] dev DEV [ parent qdisc-id | root ] [ handle qdisc-id ] [ ingress_block BLOCK_INDEX ] [ egress_block BLOCK_INDEX ] qdisc [ qdisc specific parameters ] tc [ OPTIONS ] class [ add | change | replace | delete ] dev DEV parent qdisc-id [ classid class-id ] qdisc [ qdisc specific parameters ] tc [ OPTIONS ] filter [ add | change | replace | delete | get ] dev DEV [ parent qdisc-id | root ] [ handle filter-id ] protocol protocol prio priority filtertype [ filtertype specific parameters ] flowid flow-id tc [ OPTIONS ] filter [ add | change | replace | delete | get ] block BLOCK_INDEX [ handle filter-id ] protocol protocol prio priority filtertype [filtertype specific parameters ] flowid flow-id tc [ OPTIONS ] chain [ add | delete | get ] dev DEV [ parent qdisc-id | root ] filtertype [ filtertype specific parameters ] tc [ OPTIONS ] chain [ add | delete | get ] block BLOCK_INDEX filtertype [ filtertype specific parameters ] tc [ OPTIONS ] [ FORMAT ] qdisc { show | list } [ dev DEV ] [ root | ingress | handle QHANDLE | parent CLASSID ] [ invisible ] tc [ OPTIONS ] [ FORMAT ] class show dev DEV tc [ OPTIONS ] filter show dev DEV tc [ OPTIONS ] filter show block BLOCK_INDEX tc [ OPTIONS ] chain show dev DEV tc [ OPTIONS ] chain show block BLOCK_INDEX tc [ OPTIONS ] monitor [ file FILENAME ]3. 选项OPTIONS := { [ -force ] -b[atch] [ filename ] | [ -n[etns] name ] | [ -N[umeric] ] | [ -nm | -nam[es] ] | [ { -cf | -c[onf] } [ filename ] ] [-t[imestamp] ] | [ -t[short] | [ -o[neline] ] } css Copy code FORMAT := { -s[tatistics] | -d[etails] | -r[aw] | -i[ec] | -g[raph] | -j[json] | -p[retty] | -col[or] }4. 描述tc 用于配置 Linux 内核中的流量控制。流量控制包括以下几个方面：整形（SHAPING）当流量被整形时，其传输速率会受到控制。整形不仅仅是降低可用带宽，它还用于平滑流量的突发，以改善网络行为。整形发生在出口端（egress）。调度（SCHEDULING）通过调度数据包的传输，可以改善需要交互的流量的表现，同时保证大流量传输的带宽。重新排序也称为优先级设置，仅在出口端发生。检测（POLICING）整形处理的是传输中的流量，而检测处理的是到达的流量。因此，检测发生在入口端（ingress）。丢弃（DROPPING）超出设定带宽的流量也可能会被立即丢弃，这在入口端和出口端都会发生。流量处理由三种对象控制：qdiscs、classes 和 filters。4.1. QDISCSqdisc 是 “队列规则 (queueing disciplin)” 的简称，对于理解流量控制至关重要。每当内核需要将数据包发送到接口时，它会被排入配置好的 qdisc 中。紧接着，内核会尽可能多地从 qdisc 中取出数据包，交给网络适配器驱动。简单的 QDISC 是 “pfifo”，它不进行任何处理，仅仅是一个纯粹的先进先出（FIFO）队列。它在网络接口暂时无法处理流量时，会存储这些流量。4.2. CLASSES一些 qdiscs 可以包含 classes，这些 classes 进一步包含 qdiscs——流量可能会被排入这些内部的 qdiscs 中。当内核尝试从这样的 classful qdisc 中取出数据包时，数据包可能来自任何 class。qdisc 可能例如通过尝试从某些 classes 中先取出数据包来优先处理某些类型的流量。4.3. FILTERS过滤器用于 classful qdisc 来确定数据包将被排入哪个 class。当流量到达一个具有子类的 class 时，它需要被分类。可以使用多种方法来实现分类，其中之一就是过滤器。所有附加到 class 的过滤器都会被调用，直到其中一个返回判决。如果没有做出判决，可能会有其他标准可用。这在不同的 qdisc 中有所不同。需要注意的是，过滤器存在于 qdiscs 中——它们不是事件的控制者。可用的过滤器包括：basic: 基于 ematch 表达式过滤数据包。详见 tc-ematch(8)。bpf: 使用 (e)BPF 过滤数据包，详见 tc-bpf(8)。cgroup: 基于其进程的控制组过滤数据包。详见 tc-cgroup(8)。flow, flower: 基于流量的分类器，按流量（通过可选择的键识别）过滤数据包。详见 tc-flow(8) 和 tc-flower(8)。fw: 基于 fwmark 过滤。将 fwmark 值直接映射到流量类。详见 tc-fw(8)。route: 基于路由表过滤数据包。详见 tc-route(8)。rsvp: 匹配资源预留协议（RSVP）数据包。tcindex: 基于流量控制索引过滤数据包。详见 tc-tcindex(8)。u32: 在任意数据包数据上进行通用过滤，辅助以抽象常见操作的语法。详见 tc-u32(8)。matchall: 匹配每个数据包的流量控制过滤器。详见 tc-matchall(8)。4.4. QEVENTSQdiscs 可以在 qdisc 中发生某些感兴趣的事件时调用用户配置的操作。每个 qevent 可以是未使用的，或者附加一个 block。然后将过滤器附加到该 block，使用 “tc block BLOCK_IDX” 语法。当与附件点相关的 qevent 发生时，将执行该 block。例如，数据包可以被丢弃或延迟等，具体取决于 qdisc 和相关的 qevent。示例：tc qdisc add dev eth0 root handle 1: red limit 500K avpkt 1K \ qevent early_drop block 10 tc filter add block 10 matchall action mirred egress mirror dev eth14.5. 无类 QDISCS无类 qdiscs 包括：choke: CHOKe（选择并保留响应流，选择并丢弃非响应流）是一种无类 qdisc，旨在识别并惩罚那些垄断队列的流量。CHOKe 是 RED 的一种变体，配置方式与 RED 类似。codel: CoDel（发音为 "coddle"）是一种自适应的 “无旋钮” 活跃队列管理算法（AQM），旨在解决 RED 及其变体的一些缺陷。它通过动态调整队列的丢弃概率来减少队列延迟和波动。[p|b]fifo: 最简单的 qdisc，纯粹的先进先出（FIFO）行为。按数据包或字节限制队列长度。例如，pfifo 和 bfifo 分别表示基于数据包和字节的 FIFO 队列。fq: Fair Queue Scheduler 实现 TCP 节流，并扩展到每个 qdisc 的数百万个并发流。它通过将流量公平地分配给所有流，避免了流量集中在少数流上，确保了多流环境中的公平性。fq_codel: Fair Queuing Controlled Delay 是一种将公平排队与 CoDel AQM 方案结合的队列规则。FQ_Codel 使用随机模型将传入的数据包分类到不同的流中，为每个流提供公平的带宽份额。每个流的排队由 CoDel 规则管理，避免了流内的重新排序，因为 CoDel 内部使用 FIFO 队列。fq_pie: FQ-PIE（带有比例积分控制器增强的流量排队）将 Flow Queuing 与 PIE AQM 方案结合。FQ-PIE 使用 Jenkins 哈希函数将传入的数据包分类到不同的流中，为所有流提供公平的带宽份额。每个流由 PIE 算法管理，旨在减少延迟和队列积压。gred: Generalized Random Early Detection 将多个 RED 队列组合以实现多个丢弃优先级。这对于实现保证转发（如 RFC 2597）是必需的。通过使用多个队列，可以根据流量类型进行更精细的控制。hhf: Heavy-Hitter Filter 区分小流和重流。其目标是捕捉重流，并将其移动到优先级较低的队列中，从而避免批量流量对关键流量的延迟影响。ingress: 这是一个特殊的 qdisc，专门用于入口流量控制。它允许在数据包到达接口之前进行流量整形和处理。mqprio: Multiqueue Priority Qdisc 是一种简单的队列规则，它允许使用优先级和可配置的优先级到流量类别映射，将流量流分配到硬件队列范围。这里的流量类别是指一组连续的 qdisc 类别，1:1 映射到一组硬件暴露的队列。multiq: Multiqueue 是一种针对具有多个 Tx 队列的设备优化的 qdisc。它被添加用于硬件希望避免头部阻塞的情况。它将循环遍历各个带宽，并在出队数据包之前验证与带宽相关联的硬件队列是否未停止。netem: Network Emulator 是 Linux 流量控制设施的增强功能，它允许向来自选定网络接口的数据包添加延迟、丢包、重复和其他特性。pfifo_fast: 适用于 “Advanced Router” 启用内核的标准 qdisc。包括一个三带队列，它尊重服务类型标志以及可能分配给数据包的优先级。pie: Proportional Integral controller-Enhanced (PIE) 是一种控制理论的活跃队列管理方案。它基于比例积分控制器，但旨在控制延迟。red: Random Early Detection 通过在接近配置的带宽分配时随机丢弃数据包来模拟物理拥塞。非常适合带宽非常大的应用。rr: Round-Robin qdisc 支持多队列网络设备。自 Linux 2.6.27 内核版本起被移除。sfb: Stochastic Fair Blue 是一种基于数据包丢失和链路利用历史的无类 qdisc，旨在管理拥塞，同时尝试防止非响应流（即不对拥塞标记或丢包做出反应的流）影响响应流的性能。与 RED 不同，BLUE 尝试自动确定理想的标记概率，而不需要配置标记概率。sfq: Stochastic Fairness Queueing 重新排序排队的流量，以便每个 “会话” 轮流发送数据包。tbf: Token Bucket Filter 适合将流量减速到精确配置的速率。对大带宽具有良好的扩展性。4.6. 配置无类 QDISCS在没有类 qdiscs 的情况下，无类 qdiscs 只能附加在设备的根节点上。完整的语法为：tc qdisc add dev DEV root QDISC QDISC-PARAMETERS要删除，请执行：tc qdisc del dev DEV root在没有配置 qdisc 的情况下，pfifo_fast qdisc 是自动默认的。4.7. 类别 QDISCS类别 qdiscs 包括：ATM将流映射到基础异步传输模式设备的虚拟电路中。CBQ基于类别的排队实现了丰富的链路共享层次结构。它包含了形状元素和优先级功能。形状通过基于平均数据包大小和基础链路带宽的链路空闲时间计算进行。对于某些接口，带宽可能不明确。DRR缺额轮询调度器是对随机公平排队（SFQ）的更灵活的替代方案。与 SFQ 不同，它没有内置队列——你需要添加类别，然后设置过滤器以相应地分类数据包。这对于使用具有不同设置的 RED qdisc 来处理特定流量可能很有用。没有默认类别——如果数据包无法分类，则会被丢弃。DSMARK根据 TOS 字段对数据包进行分类，根据分类更改数据包的 TOS 字段。ETSETS qdisc 是一个合并了 PRIO 和 DRR qdiscs 功能的调度程序。ETS 使配置一组严格和带宽共享的带宽变得简单，以实现 802.1Qaz 中描述的传输选择。HFSC分层公平服务曲线为叶类别提供精确的带宽和延迟分配，并公平分配多余的带宽。与 HTB 不同，它通过丢弃数据包来实现低延迟，从而使交互式会话受益。HTB分层令牌桶实现了具有丰富链路共享层次结构的类别，强调符合现有实践。HTB 促进了对类别的带宽保证，同时允许指定类别间共享的上限。它包含基于 TBF 的形状元素，并可以对类别进行优先级排序。PRIOPRIO qdisc 是一个非形状容器，具有可配置数量的类别，这些类别按照顺序出队。这允许轻松地对流量进行优先级排序，其中较低的类别只有在较高类别没有数据包可用时才会发送。为了方便配置，默认情况下会遵守服务类型位。QFQ快速公平排队是一个 O(1) 调度器，提供接近最优的保证，它是第一个以常数成本实现这一目标的调度器，并且在处理组和数据包长度方面也保持常数。QFQ 算法没有循环，并使用非常简单的指令和数据结构，适合硬件实现。4.8. 操作原理类别形成一个树形结构，每个类别有一个单一的父节点。一个类别可以有多个子节点。某些 qdiscs 允许在运行时添加类别（如 CBQ、HTB），而其他 qdiscs（如 PRIO）则以静态数量的子节点创建。允许动态添加类别的 qdiscs 可以有零个或多个子类别，用于排队流量。此外，每个类别包含一个叶子 qdisc，默认具有 pfifo 行为，虽然可以用其他 qdisc 替代。这个 qdisc 可能再次包含类别，但每个类别只能有一个叶子 qdisc。当数据包进入类别 qdisc 时，它可以被分类到其中的一个类别。分类的三个标准如下，但并非所有 qdiscs 都会使用这三个标准：tc 过滤器如果 tc 过滤器附加到类别上，它们首先被查阅以获取相关指令。过滤器可以匹配数据包头的所有字段，以及由 iptables 应用的防火墙标记。服务类型（TOS）一些 qdiscs 内置了根据 TOS 字段分类数据包的规则。skb->priority用户空间程序可以使用 SO_PRIORITY 选项在 'skb->priority' 字段中编码类别 ID。树中的每个节点可以有自己的过滤器，但较高级别的过滤器也可以直接指向较低级别的类别。如果分类失败，数据包会被排队到该类别附加的叶子 qdisc 上。具体细节请参考 qdisc 的手册页。4.9. 命名所有 qdiscs、类别和过滤器都有 ID，这些 ID 可以指定，也可以自动分配。ID 由一个主编号和一个次编号组成，以冒号分隔 - 主编号:次编号。主编号和次编号都是十六进制数字，并且限制在 16 位内。有两个特殊值：root 由全 1 的主编号和次编号表示，未指定则为全 0。QDISCS一个可能拥有子节点的 qdisc 被分配一个主编号，称为 'handle'，而次编号命名空间则留给类别。handle 表达为 '10:'。通常，预计会有子节点的 qdiscs 会明确分配一个 handle。CLASSES居于 qdisc 下的类别共享它们的 qdisc 主编号，但每个类别都有一个称为 'classid' 的单独次编号，该编号与其父类别无关，仅与其父 qdisc 相关。类别的命名遵循与 qdiscs 相同的习惯。FILTERS过滤器有一个三部分 ID，只有在使用哈希过滤器层次结构时才需要。4.10. 参数以下参数在 TC 中被广泛使用。有关其他参数，请参见个别 qdiscs 的手册页。速率（RATES）带宽或速率。这些参数接受浮点数，可能后跟单位（支持 SI 和 IEC 单位），或者是浮点数后跟 '%' 字符以指定为设备速度的百分比（例如 5%，99.5%）。警告：将速率指定为百分比意味着是当前速度的一个分数；如果速度发生变化，值不会重新计算。bit 或裸数每秒位数kbit每秒千比特mbit每秒兆比特gbit每秒千兆比特tbit每秒太比特bps每秒字节数kbps每秒千字节mbps每秒兆字节gbps每秒千兆字节tbps每秒太字节指定 IEC 单位时，替换 SI 前缀（k-、m-、g-、t-）为 IEC 前缀（ki-、mi-、gi-、ti-）。TC 内部将速率存储为 32 位无符号整数，单位为 bps，因此可以指定最大速率为 4294967295 bps。时间（TIMES）时间长度。可以指定为浮点数后跟可选单位：s、sec 或 secs整秒ms、msec 或 msecs毫秒us、usec、usecs 或裸数微秒TC 定义了自己的时间单位（等于微秒），并将时间值存储为 32 位无符号整数，因此可以指定最大时间值为 4294967295 微秒。大小（SIZES）数据量。可以指定为浮点数后跟可选单位：b 或裸数字节kbit千比特kb 或 k千字节mbit兆比特mb 或 m兆字节gbit千兆比特gb 或 g千兆字节TC 内部将大小存储为 32 位无符号整数，单位为字节，因此可以指定最大大小为 4294967295 字节。值（VALUES）其他没有单位的值。这些参数默认按十进制解释，但可以通过添加 '0' 或 '0x' 前缀分别指示 TC 按八进制或十六进制解释它们。5. TC 命令以下命令适用于 qdiscs、类别和过滤器：add向节点添加 qdisc、类别或过滤器。对于所有实体，必须传递父节点，方法是通过其 ID 或直接附加到设备的根节点。当创建 qdisc 或过滤器时，可以使用 handle 参数进行命名。类别使用 classid 参数进行命名。delete通过指定其 handle（也可以是 'root'）删除 qdisc。所有子类别及其叶子 qdiscs 将被自动删除，以及任何附加到它们的过滤器。change某些实体可以“就地”修改。与 'add' 共享语法，唯一的区别是 handle 不能更改，父节点也不能更改。换句话说，change 不能移动节点。replace在现有节点 ID 上执行几乎原子的删除/添加操作。如果节点尚不存在，则会创建它。get显示给定接口 DEV、qdisc-id、优先级、协议和过滤器 ID 的单个过滤器。show显示附加到给定接口的所有过滤器。必须传递有效的父 ID。link仅适用于 qdiscs，执行替换，其中节点必须已经存在。5.1. 监控tc 工具可以监控由内核生成的事件，例如添加/删除 qdiscs、过滤器或操作，或修改现有的。以下命令适用于监控：file如果给出 file 选项，tc 不会监听内核事件，而是打开给定的文件并转储其内容。文件必须是二进制格式并包含 netlink 消息。5.2. 选项-b, -b filename, -batch, -batch filename从提供的文件或标准输入中读取命令并执行。第一次失败将导致 tc 终止。-force在批处理模式下遇到错误时不要终止 tc。如果在执行命令时发生错误，应用程序的返回码将为非零。-o, -oneline将每条记录输出到单行，用 \ 字符替换换行符。这在你想用 wc(1) 统计记录数或用 grep(1) 搜索输出时很方便。-n, -net, -netns 将 tc 切换到指定的网络命名空间 NETNS。实际上，这只是简化了执行以下操作：ip netns exec NETNS tc [ OPTIONS ] OBJECT { COMMAND | help }为：tc -n[etns] NETNS [ OPTIONS ] OBJECT { COMMAND | help }-N, -Numeric直接打印协议、作用域、dsfield 等的数字，而不是将其转换为可读名称。-cf, -conf 指定配置文件的路径。此选项与其他选项（如 -nm）一起使用。-t, -timestamp当 tc 监控运行时，在事件消息之前打印时间戳，格式为：Timestamp: <Day> <Month> <hh:mm:ss> <YYYY> <usecs> usec-ts, -tshort当 tc 监控运行时，在事件消息之前打印简短时间戳，格式为：[<YYYY>-<MM>- T<hh:mm:ss>.<ms>]5.3. 格式show 命令有额外的格式化选项：-s, -stats, -statistics输出更多有关数据包使用的统计信息。-d, -details输出关于速率和单元大小的更详细信息。-r, -raw输出句柄的原始十六进制值。-p, -pretty对于 u32 过滤器，解码偏移量和掩码值为基于 TCP/IP 的等效过滤器命令。在 JSON 输出中，添加空格以提高可读性。-iec以 IEC 单位打印速率（例如 1K = 1024）。-g, -graph以 ASCII 图形显示类别。如果指定了 -s 选项，则在每个类别下打印通用统计信息。类别只能通过 dev 选项过滤。-ccolor配置彩色输出。如果省略参数或选择 always，则无论标准输出状态如何，彩色输出都会启用。如果选择 auto，则在启用彩色输出之前会检查标准输出是否为终端。如果选择 never，则禁用彩色输出。如果指定多次，最后一个参数将优先。如果同时给出了 -json，则此标志会被忽略。-j, -json以 JSON 格式显示结果。-nm, -name从 /etc/iproute2/tc_cls 文件或由 -cf 选项指定的文件中解析类别名称。此文件仅为类别 ID 到类别名称的映射：# 这里是注释 1:40 voip # 这里是另一个注释 1:50 web 1:60 ftp 1:2 home如果指定了 -nm 而未指定 -cf，且 /etc/iproute2/tc_cls 文件不存在，tc 不会失败，这使得可以传递 -nm 选项以创建 tc 别名。-br, -brief仅打印识别过滤器和操作（句柄、cookie 等）以及统计信息所需的基本数据。此选项目前仅支持 tc filter show 和 tc actions ls 命令。5.4. 示例tc -g class show dev eth0以 ASCII 图形显示 eth0 接口上的类别。tc -g -s class show dev eth0以 ASCII 图形显示类别，并在每个类别下方显示统计信息。6. 历史tc 由 Alexey N. Kuznetsov 编写，并在 Linux 2.2 中添加。7. 参见tc-basic(8)`, `tc-bfifo(8)`, `tc-bpf(8)`, `tc-cake(8)`, `tc-cbq(8)`, `tc-cgroup(8)`, `tc-choke(8)`, `tc-codel(8)`, `tc-drr(8)`, `tc-ematch(8)`, `tc-ets(8)`, `tc-flow(8)`, `tc-flower(8)`, `tc-fq(8)`, `tc-fq_codel(8)`, `tc-fq_pie(8)`, `tc-fw(8)`, `tc-hfsc(7)`, `tc-hfsc(8)`, `tc-htb(8)`, `tc-mqprio(8)`, `tc-pfifo(8)`, `tc-pfifo_fast(8)`, `tc-pie(8)`, `tc-red(8)`, `tc-route(8)`, `tc-sfb(8)`, `tc-sfq(8)`, `tc-stab(8)`, `tc-tbf(8)`, `tc-tcindex(8)`, `tc-u32(8)用户文档请访问 http://lartc.org/，但请将 bug 报告和补丁发送到：netdev@vger.kernel.org8. 作者手册页由 bert hubert（ahu@ds9a.nl）维护。iproute2 2001年12月16日 TC(8)
对于 iptables 学习，反复看过 man iptables 命令，但是缺乏归纳总结，不用就忘了。现在好了，直接让 ChatGPT 帮我画一些系统框图辅助记忆。Q:帮我用 Mermaid 画一个系统框图描述 iptables 所有 Chain 的处理顺序，并且描述其生效范围。graph LR INPUT --> PREROUTING PREROUTING --> ROUTING ROUTING --> FORWARD FORWARD --> POSTROUTING POSTROUTING --> OUTPUT 这个图表演示了 iptables 中 5 个 Chain 处理的先后关系，其中：INPUT Chain 用于处理进入本地计算机的网络数据包；PREROUTING Chain 用于处理数据包路由前的操作，例如修改目标 IP 地址；ROUTING Chain 用于处理数据包的路由过程，例如根据路由表进行转发；FORWARD Chain 用于处理转发给其他计算机的网络数据包；POSTROUTING Chain 用于处理数据包路由后的操作，例如修改源 IP 地址；OUTPUT Chain 用于处理从本地计算机发出的网络数据包；Q:再帮我给每一个 Chain 规则生成一个详细的命令。INPUT Chain：允许来自特定 IP 地址的 SSH 连接 iptables -A INPUT -p tcp -s 192.168.1.100 --dport 22 -j ACCEPTPREROUTING Chain：将来自特定 IP 地址的数据包重定向到另一台计算机iptables -t nat -A PREROUTING -s 192.168.1.100 -j DNAT --to-destination 192.168.1.200ROUTING Chain：将来自特定 IP 地址的数据包标记为优先级 1iptables -t mangle -A PREROUTING -s 192.168.1.100 -j MARK --set-mark 1FORWARD Chain：允许从特定网卡转发特定端口的数据包iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 80 -j ACCEPTPOSTROUTING Chain：将来自特定 IP 地址的数据包修改为指定 IP 地址并从指定网卡发出iptables -t nat -A POSTROUTING -s 192.168.1.100 -o eth0 -j SNAT --to-source 10.0.0.1OUTPUT Chain：允许从本地计算机发出到特定 IP 地址和端口的数据包iptables -A OUTPUT -p tcp -d 192.168.1.100 --dport 80 -j ACCEPTQ:再帮我用 Mermaid 描述下 Table 和 Chain 之间的系统关系。graph LR subgraph Table_Chain_Relationships Filter_Table -->|Table| INPUT Filter_Table -->|Table| OUTPUT Filter_Table -->|Table| FORWARD Mangle_Table -->|Table| PREROUTING Mangle_Table -->|Table| INPUT Mangle_Table -->|Table| OUTPUT Mangle_Table -->|Table| FORWARD Mangle_Table -->|Table| POSTROUTING Raw_Table -->|Table| PREROUTING Raw_Table -->|Table| OUTPUT Security_Table -->|Table| INPUT Security_Table -->|Table| OUTPUT Security_Table -->|Table| FORWARD NAT_Table -->|Table| PREROUTING NAT_Table -->|Table| OUTPUT NAT_Table -->|Table| POSTROUTING end
目录 [隐藏] 样例探索结论 GCC 优化过于复杂，这里探索 GCC 对未使用的代码和变量以及文件的优化策略。3.10 Options That Control Optimization6.3.3 Reducing Size of Executables with Unused Subprogram/Data Elimination样例如下两个文件。$ tree ├── main.c └── unused.cmain.c 文件内容如下。#include <stdio.h> // __attribute__((used, section(".linker_entry_list"))) int main_extern_unused_var = 0; // __attribute__((used, section(".linker_entry_list"))) static int main_static_unused_var = 0; // __attribute__((used)) static int main_static_unused_func(void) { printf("static unused function.\n"); return 0; } // __attribute__((used)) int main_extern_unused_func(void) { printf("extern unused function.\n"); return 0; } int main(int argc, char* argv[]) { printf("hello world!\n"); return 0; }unused.c#include <stdio.h> // __attribute__((used, section(".linker_entry_list"))) int other_extern_unused_var = 0; // __attribute__((used, section(".linker_entry_list"))) static int other_static_unused_var = 0; // __attribute__((used)) static int other_static_unused_func(void) { printf("static unused function.\n"); return 0; } // __attribute__((used)) int other_extern_unused_func(void) { printf("extern unused function.\n"); return 0; }探索默认编译选项，通过可执行文件确定所有变量和函数都未优化。$ gcc -o main.app main.c unused.c readelf -a main.app |grep unused 37: 0000000000004018 4 OBJECT LOCAL DEFAULT 26 main_static_unused_var 38: 0000000000001149 27 FUNC LOCAL DEFAULT 16 main_static_unused_func 40: 0000000000004020 4 OBJECT LOCAL DEFAULT 26 other_static_unused_var 41: 00000000000011a5 27 FUNC LOCAL DEFAULT 16 other_static_unused_func 57: 0000000000001164 27 FUNC GLOBAL DEFAULT 16 main_extern_unused_func 68: 00000000000011c0 27 FUNC GLOBAL DEFAULT 16 other_extern_unused_func 69: 000000000000401c 4 OBJECT GLOBAL DEFAULT 26 other_extern_unused_var 70: 0000000000004014 4 OBJECT GLOBAL DEFAULT 26 main_extern_unused_var-O1 -O2 -Os 选项，所有static 链接属性的变量和函数被用优化掉。$ gcc -Os -o main.app main.c unused.c $ readelf -a main.app |grep unused 53: 0000000000001169 21 FUNC GLOBAL DEFAULT 16 main_extern_unused_func 64: 000000000000117e 21 FUNC GLOBAL DEFAULT 16 other_extern_unused_func 65: 0000000000004018 4 OBJECT GLOBAL DEFAULT 26 other_extern_unused_var 66: 0000000000004014 4 OBJECT GLOBAL DEFAULT 26 main_extern_unused_var为 static 链接属性的变量增加 __attribute__((used)) 属性。优化被抑制。__attribute__((used)) static int other_static_unused_var=0; __attribute__((used)) static int other_static_unused_func(void) { printf("static unused function.\n"); return 0; }$ gcc -Os -o main.app main.c unused.c $ readelf -a main.app |grep unused 29: 0000000000001169 21 FUNC LOCAL DEFAULT 16 main_static_unused_func 30: 0000000000004018 4 OBJECT LOCAL DEFAULT 26 main_static_unused_var 40: 0000000000001193 21 FUNC LOCAL DEFAULT 16 other_static_unused_func 41: 0000000000004020 4 OBJECT LOCAL DEFAULT 26 other_static_unused_var 57: 000000000000117e 21 FUNC GLOBAL DEFAULT 16 main_extern_unused_func 68: 00000000000011a8 21 FUNC GLOBAL DEFAULT 16 other_extern_unused_func 69: 000000000000401c 4 OBJECT GLOBAL DEFAULT 26 other_extern_unused_var 70: 0000000000004014 4 OBJECT GLOBAL DEFAULT 26 main_extern_unused_var增加 -Wl,--gc-sections 选项，所有的 unused.c 变量和函数，以及 main.c 中的变量全部被优化。$gcc -Wl,--gc-sections -o main.app main.c unused.c $readelf -a main.app |grep unused 37: 0000000000001149 27 FUNC LOCAL DEFAULT 16 main_static_unused_func 53: 0000000000001164 27 FUNC GLOBAL DEFAULT 16 main_extern_unused_func同时使用 -Wl,--gc-sections 和 -O1。进一步优化掉 main.c 中的 static 函数，只剩下最后一个未使用的 extern 函数。$ gcc -Wl,--gc-sections -O1 -o main.app main.c unused.c $ readelf -a main.app |grep unused 52: 0000000000001149 30 FUNC GLOBAL DEFAULT 16 main_extern_unused_func同时使用 -Wl,--gc-sections 和 -O2 或者 -Os 。所有未使用的函数、变量全部被优化。$ gcc -Wl,--gc-sections -O2 -o main.app main.c unused.c 或者额外指定 -ffunction-sections ，进一步把未使用的函数优化掉。$gcc -Wl,--gc-sections -ffunction-sections -fdata-sections -o main.app main.c unused.c对于--gc-sections ，通过设置 KEEP 链接抑制变量的优化。注意，并不能对函数使用属性。__attribute__((used, section(".linker_entry_list"))) int main_extern_unused_var = 0; __attribute__((used, section(".linker_entry_list"))) static int main_static_unused_var = 0;.linker_entry_list : { KEEP(*(SORT(.linker_entry_list*))); }$gcc -Wl,--gc-sections -ffunction-sections -fdata-sections -o main.app main.c unused.c -T x86_64.lds $ readelf -a main.app |grep unused 25: 3ffbdb64 4 OBJECT LOCAL DEFAULT 13 main_static_unused_var 27: 3ffbdb6c 4 OBJECT LOCAL DEFAULT 13 other_static_unused_var 88: 3ffbdb68 4 OBJECT GLOBAL DEFAULT 13 other_extern_unused_var 98: 3ffbdb60 4 OBJECT GLOBAL DEFAULT 13 main_extern_unused_var结论选项结果默认选项/-O0不优化-O1/2/3/s按链接属性优化，优化未使用的 static 链接属性的变量和函数。可以通过 __attribute__((used)) 抑制该优化。-Wl,--gc-sections按未使用的变量、函数、文件进行优化，默认优化未使用变量（-fdata-sections）和未使用文件，通过指定 （-ffunction-sections）选择未优化函数。对于 Data 可以使用 KEEP 抑制该优化。
目录 [隐藏] TBBR-CLIENT固件设计BL1BL2BL31BL33证书链 这里详细介绍基于 ARMv8-A AArch64 固件安全相关，包含 TF-A（Trust Firmware A）。以及基于TF-A OEM厂商对 U-Boot 和 Kernel 实现的安全启动流程 SecureBoot。https://trustedfirmware-a.readthedocs.io/en/latestTBBR-CLIENThttps://developer.arm.com/documentation/den0006/latest对于 TF-A，ARMv8-A AArch64 架构需要满足如下设计需求。区分不同的异常等级（Exception Level）；对于不同的安全等级区分了可信的世界（Trust World）和非可信世界（Non-Trusted World）。我们的应用程序，内核都工作它认为不可信世界，所以没有办法直接访问一些资源。权限等级描述实现方EL-0应用权限CPU支持EL-1内核权限CPU支持EL2虚拟权限等级CPU支持EL-3安全权限等级CPU或者内部安全模块支持​不同安全等级下访问不同的硬件资源。例如一些 OTP/eFUSE区域，在 EL0-EL2 异常等级应用程序是没有办法直接访问的。如果需要访问这些资源，通过 SMC 共享内存通过工作更高等级例如 EL3 的程序实现间接访问。有了如上认识，接下来我们梳理 TF-A 约定的在不同 EL下的引导程序。固件设计通常地，对于ARM AArch64 如下开机执行如下引导顺序。提示：对于 AArch32 ，这里 BL31 为 BL32。BL1BL1 作为 ROM Bootloader 工作在最高安全等级 EL3，它拥有独立的 Trusted ROM 和 SRAM 区域。BL1 会在平台指定的基地址载入 BL2，并且传递控制权到 BL2，此时 BL2 工作在SE-L1。BL2BL2 初始化终端 Console；初始化次级引导的存储，用以接下来的加载。使能 MMU 和以及它需要访问的存储空间；保留部分存储用以次级 EL3 的引导；BL31BL2 载入 BL31，移交控制权 EL3 到 BL31。BL31 执行如下架构初始化：初始化其关注的系统寄存器；初始化每个 CPU 的数据架构，提供 Cache 访问，用以提速。该初始化并行在多个 CPU 进行。同时 BL31还会执行如下平台初始化：初始化终端 Console；初始化 MMU；初始化通用中断控制器；BL33BL33 通常就是 U-Boot，此次往后的应用都工作平台不信任的世界。但是此时 BL31 已经常驻内存，如果需要访问安全世界的资源，需要通过 SMC（共享内存） 方式调用BL31接口。证书链如上引导，除了 BL1 是固化在 ROM，其他都是在非可信的外部存储，例如 Flash、EMMC。那么平台又是如何保证其的安全的。如下证书链导出过程指示了多个用以不同固件签名的私钥生成。如上框图指示，厂商会预制一个公钥到硬件，基于该公钥的私钥我们签名了多个证书。Non-Trusted Firmware Updater certificate该证书对应私钥用以非可信固件的签名，公钥用以升级过程的验签。Trusted Boot Firmware certificate该证书对应私钥用以可信固件的签名，公钥用以升级过程的验签。Trusted Key certificate可信秘钥证书对应私钥（OEM Private KEY）又签发了 Trusted Key certificate，包含三个公钥，分别对应：Debug Public Key。Trusted World Public Key。该证书用以 BL2 和 BL31 的认证。Non-Trusted World Public Key。这里用以 BL33，即 U-Boot 的认证。提示：这里有个关键点：对于 Trusted Key certificate 的签发通常芯片厂商会释放工具，相当于开放私钥。之前一直想不明白既然厂商公开了私钥任何人都可以更改后续证书？其实是因为Trusted Key certificate 是烧写在固定地址的 OTP （eFUSE）区域，只可以烧写一次。所以一旦在生产环节烧写了 Trusted Key certificate，那么芯片对应的 OEM Private KEY 也就唯一了。最后我们确认下 BL31 的验签流程。如下图指示，通过厂商预制根公钥 ROTPK 对生产阶段烧写的 Trusted Key Certificate 验签，之后得到可信的 Trusted World Public Key ，用该公钥验证 BL31 Key Certificate，得到 BL31 Content Certificate PK ，使用改公钥验证BL31 Content Certificate，之后得到 BL31固件的hash值。确认 BL31 Image 的可信。+------------------+ +-------------------+ | ROTPK/ROTPK Hash |------>| Trusted Key | +------------------+ | Certificate | | (Auth Image) | /+-------------------+ / | / | / | / | L v +------------------+ +-------------------+ | Trusted World |------>| BL31 Key | | Public Key | | Certificate | +------------------+ | (Auth Image) | +-------------------+ / | / | / | / | / v +------------------+ L +-------------------+ | BL31 Content |------>| BL31 Content | | Certificate PK | | Certificate | +------------------+ | (Auth Image) | +-------------------+ / | / | / | / | / v +------------------+ L +-------------------+ | BL31 Hash |------>| BL31 Image | | | | (Data Image) | +------------------+ | | +-------------------+ DIAGRAM 2.相同的流程，通过厂商预制根公钥 ROTPK 对生产阶段烧写的 Trusted Key Certificate 验签，之后得到可信的 Non-Trusted World Public Key 对于BL33 的验证流程以及后续的对 Kernel 的验证流程就是 SecureBoot。
Linux capabilities对于权限细节参考man 7 capabilities；对于-+= eip操作符参考 man 3 cap_from_text；文档中命令setcap、getcap等都可以通过man命令查看；我需要在VS Code 调试NuttX应用，它的Sim应用需要cap_net_raw,cap_net_admin权限，否则就无法正常操作HCI设备。当然网上有一些帖子尝试将VS Code或者GDB运行在root用户，显然这样操作是非常不优雅的。所以这里尝试给需要调试的应用利用linux capabilities直接给可执行文件设置权限。$ sudo setcap 'cap_net_raw,cap_net_admin=ep' nuttx$ getcap nuttx nuttx = cap_net_admin,cap_net_raw+ep此时运行程序，可以通过capsh或者getcaps检查进程确认已经设置成功。$ getpcaps $(pidof nuttx) 283131: = cap_net_admin,cap_net_raw+ep$ cat /proc/283131/status|grep Cap CapInh: 0000000000000000 CapPrm: 0000000000003000 CapEff: 0000000000003000 CapBnd: 000001ffffffffff CapAmb: 0000000000000000 $ capsh --decode=000001ffffffffff WARNING: libcap needs an update (cap=40 should have a name). 0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39,40此时通过VS Code启动调试后发现其却没有了需要的文件权限。$ getpcaps $(pidof nuttx) 284336: = | | \_ /usr/share/code/code --ms-enable-electron-run-as-node /usr/share/code/resources/app/out/bootstrap-fork --type=ptyHost | | | \_ /usr/bin/bash | | | \_ /usr/bin/bash | | | | \_ /bin/sh /tmp/Microsoft-MIEngine-Cmd-nptpb2ki.pex | | | | \_ /usr/bin/gdb --interpreter=mi --tty=/dev/pts/4 | | | | \_ /home/lijie/my_project/nuttxspace/nuttx/nuttx看起来是父进程/usr/bin/gdb 启动nuttx进程的时候并没有使用其文件文件属性。直接测试了gdb ./nuttx确认和上面现象一致，strace确认其通过ptrace创建进程。ptrace(PTRACE_SETOPTIONS, 285286, NULL, PTRACE_O_TRACESYSGOOD|PTRACE_O_TRACECLONE|PTRACE_O_EXITKILL) = 0怀疑这里是gdb进程没有cap_sys_ptrace权限，添加后再次通过VS Code启用nuttx调试发现其进程已经具有我们需要的权限。$ sudo setcap 'cap_sys_ptrace+eip' /usr/bin/gdb $ getcap /usr/bin/gdbserver /usr/bin/gdbserver = cap_sys_ptrace+eip $ getcap nuttx nuttx = cap_net_admin,cap_net_raw+ep
众所周知，udp是无连接的，所以忽略udp接收方状态，是否监听对应udp端口以及ip是否存在，直接向udp发送数据，会发生什么？为了搞清楚这个问题，我们尝试在局域网建立收发设备，然后抓包分析。端口不可达从抓包来看，udp数据正常发出。对端设备回复icmp指示端口不可达。ip 不可达当我们把接收端设备关机，此时udp数据正常发出，直到arp连续三次请求失败。此时抓包显示udp包已经不发送了。此时，我们跟踪sytem call 确定，发用户空间的sendto 接口仍然显示成功。sendto(3, "012345679abcdef\0", 16, 0, {sa_family=AF_INET, sin_port=htons(54321), sin_addr=inet_addr("192.168.28.207")}, 16) = 16此时通过查看arp缓存确定ip状态已经标记为INCOMPLETE。$ip neigh show 192.168.28.207 dev wlp0s20f3 INCOMPLETE尝试手动更新arp缓存的mac地址和状态到REACHABLE。$sudo ip neigh change 192.168.28.207 lladdr dc:a6:32:3f:94:36 nud reachable dev wlp0s20f3提示：对于如上命令和状态值详细可以参考man ip-neighbour。此时udp又重新发了出来。值得一提的是，在路由器查看arp缓存，发现不管设备断开多久，状态都不会变更为INCOMPLETE而始终是STALE。$ip neigh show 192.168.28.207 dev br-lan lladdr dc:a6:32:3f:94:36 STALE
vscode 通过数据断点分析segmentation fault通常地，程序异常，我们借助vscode在ubuntu主动调试，程序会自动在异常代码位置停下来，但是异常分析通常不会这么简单，而是一连串连锁反应。这里尝试分析，因为程序异常，非法写入了一段正常内存，从而导致多个链路之后异常发生。而对于此问题，最常使用的调试手段就是添加数据断点。通常直接在某段代码位置添加的代码为函数断点，程序会自动在该位置停下来。同样地，我们可以添加条件断点：当某个判断条件（例如，变量值为真）满足的时候；当某段代码连续执行N次的时候；当某个变量（内存）被读写的时候；如上前两个条件断点，vsocde 里面的c/c++ extension 已经支持，可以在ide里面点击断点（红色小点）直接编辑，但是最后一个为数据断点还不支持。提示：vscode 断点更多信息参考https://code.visualstudio.com/docs/editor/debugging#_advanced-breakpoint-topics。需要借助debug console 键入gdb（gnu debugger）命令，如上操作，成功初始化device_scan_ 成员变量后，程序不再会对变量进行写操作，但是之后该对象的一个成员变量值被异常写入了非法值，当再次用该对象指针打印日志函数时候发生异常。此时通过p命令查看变量并获取变量首地址。-exec p device_scan_ #打印变量 -exec p &device_scan_.data #获取变量地址通过watch命令监控该地址的写操作。-exec watch *0x9ac920 #监控变量写操作提示：如上p、watch命令可以参考gdb手册，https://sourceware.org/gdb/current/onlinedocs/gdb/Set-Watchpoints.html#Set-Watchpoints。同时可以通过awatch监控读写，rwatch监控写操作。之后全速运行，发现程序在另外一个非法访问该变量位置停了下来，并且通过 Old Value和New Value提示了值变化。通过分析上下文就可以定位到引发异常的真正原因。
目录 [隐藏] 地址规范地址关键字socket-connect:\<domain>:\<protocol>:\<remote-address>tcp:\<host>:\<port>tcp-listen:\<port>unix-connect:<fille_name>unix-listen:<fille_name>unix-sendto:<file_name>地址选项forkreuseaddrtype样例远程访问目标机器的unix-domain socket监听unix socket重定向串口 socathttp://www.dest-unreach.org/socat/。提示：如下翻译来自 man 1 socat，未完待续……概述socat 是一个命令行工具，用以建立一个双向字节流交换数据。该字节流又可以被构造成不同地址类型，大量的地址选项也同样可以被作用于该流，因此socat非常强大。filan 是一个打印文件描述符的工具，已经被重构用以调试socat，当然，也可以另做它用。procan 是一个打印进程信息的工具，同样被重构用以调试socat。socat 实例包含如下生命周期。init 阶段解析命令参数，初始化日志打印；open阶段打开第一个地址并且转发到第二个地址字节流，此阶段可能会阻塞，因此类似socks的复杂地址选项，需要在连接请求或认证对话框完成后才会开始下一阶段。transfer阶段通过select 参数观察源地址流的读写操作，当可读或者可写时，socat完成到目的流数据转发。当任意一方流eof，closing阶段开始，socat 主动发送eof操作到对等需要关闭的另外一方尝试优雅的关闭它的写操作的字节流，在运行的时间范围内，socat也可能会继续传输数据到另外一个方向，直到所有的通道被关闭。地址规范命令的地址参数为用户给socat 建立字节流的必要信息。一个地址规范通常包含一个地址类型的关键词，零或者多个地址参数通过":"间隔，零或者多个地址选项通过","间隔。关键词指定地址类型（tcp4、open、exec），而对于一些关键字存在同义词（例如 “-”代表stdio，tcp默认为tcp4），这里的地址类型关键字不区分大小写。对于一些特殊地址类型，关键字可能会被省略，地址关键字socket-connect:\<domain>:\<protocol>:\<remote-address>tcp:\<host>:\<port>通过tcp/ip 4或者6，连接由host、port指定的地址。由地址标准决定ip版本，域名解析。选项组：FD,SOCKET,IP4,IP6,TCP,RETRY有用的选项：crnl, bind, pf, connect-timeout, tos, mtudiscover, mss, nodelay, nonblock, sourceport, retry, readbytestcp-listen:\<port>监听由port 指定的端口，并接收（accept）TCP/IP连接，ip版本为4或者地址选项pf指定，对应选项（socat -4、-6），也可通过环境变量CAT_DEFAULT_LISTEN_IP配置，注意，打开该地址后通常会阻塞进程，直到一个客户端连接。选项组：FD,SOCKET,LISTEN,CHILD,RANGE,IP4,IP6,TCP,RETRY；有用的选项： crnl, fork, bind, range, tcpwrap, pf, max-children, backlog, mss, su, reuseaddr, retry, cool-write；unix-connect:<fille_name>指定地址file_name的unix domain socket，如果未指定地址，或者该地址不是一个unix 域亦或者或者没有进程绑定此地址，则报错。选项组： FD,SOCKET,NAMED,RETRY,UNIX。有用的选项：bind。unix-listen:<fille_name>监听file_name指定地址的unix socket，并且接受（accept）新的连接，同样地，如果指定了地址但不是一个unix domain地址则报错。当然尽管指定了一个unix 域地址，也有可能会因为某个原因（比如前一个连接未unlink）导致绑定（bind）失败。注意，打开该地址后通常会阻塞进程，直到一个客户端连接。从socat 1.4.3 开始，关闭socket后会自动删除对应的文件系统条目（同样地，建议查看unlink-close选项）。选项组：FD,SOCKET,NAMED,LISTEN,CHILD,RETRY,UNIX；有用的选项：UNIX-CONNECT, UNIX-RECVFROM, UNIX-RECV, TCP-LISTEN；unix-sendto:<file_name>同file_name指定 unix 域socket通信，从对等的socket发送和接收包。注意可能需要绑定本地socket到一个地址（例如,/tmp/sock1该地址需要不存在）。地址选项fork在建立一个连接后，通过子进程通道完成数据处理，让其同父进程保持尝试产生更多连接，不管是监听还是循环接收。OPENSSL-CONNECT和 OPENSSL-LISTENreuseaddr运行其他sockets绑定（bind）某个地址，即使该地址（比如本地的某个端口）已经被socat使用。typesocket type,详细参考 man socket。nameLinuxFreeBSDNetBSDOpenBSDSolarisAIXCygwinMac OS XHP-UXPF_LOCAL111111111PF_INET222222222PF_APPLETALK51616161616161616PF_INET6102824242624-3022PF_PACKET17--------SOCK_STREAM111121111SOCK_DGRAM222212222SOCK_RAW333343333SOCK_SEQPACKET555565555SOCK_DCCP(6)--------SOCK_PACKET10--------IPPROTO_IP000000000IPPROTO_TCP666666666IPPROTO_UDP171717171717171717IPPROTO_DCCP33--------IPPROTO_SCTP132132--132132---IPPROTO_RAW255255255255255----SOL_SOCKET16553565535655356553565535655356553565535SOL_IP000000000SOL_TCP666666666SOL_UDP17-----17--SOL_IPV6414141414141-4141SOL_PACKET263--------SOL_DCCP269--------样例远程访问目标机器的unix-domain socket调试需求，这里把目标机器的unix domain socket（/tmp/agent.socket）通过socat 映射到调试的主机上面。# netstat -lp Active UNIX domain sockets (only servers) Proto RefCnt Flags Type State I-Node PID/Program name Path unix 2 [ ACC ] SEQPACKET LISTENING 15143198 13519/agent /tmp/agent.socket目标机器连接改unix socket，并且通过tcp socket 在局域网监听socat unix-connect:/tmp/agent.socket,type=5 tcp-listen:54328调试主机从局域网连接如上端口，然后存放在本地同名的unix socketsocat unix-listen:/tmp/agent.socket,type=5 tcp-connect:192.168.28.153:54328提示:如上的socket type 参考socket type章节，type=5对应SOCK_SEQPACKET，如果type不一致socat会提示错误的协议类型（the wrong protocol type）。注意:如上通过tcp（默认 SOCK_STREAM ）按照字节流转发unix socket（SEQPACKET）有序包会tcp 这端会出现粘包，所以更改成了udp（SOCK_DGRAM）转发。socat unix-connect:/tmp/agent.socket,type=5 udp-listen:54328 socat unix-listen:/tmp/agent.socket,type=5 udp-connect:192.168.28.153:54328监听unix socketln real.sock real.sock~ socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CONNECT:real.sock~ socat UNIX-LISTEN:fake.sock,fork TCP-CONNECT:127.0.0.1:6000 mv fake.sock real.sock重定向串口重定向到终端socat /dev/ttyUSB0,b115200,raw,echo=0,crnl -重定向到tcp serversocat TCP-LISTEN:2002 PTY,link=ttyV2,raw,crnl
get uboot U_BOOT_CMD usage学习到一个奇淫技能，这里总结如下。方便以后工程实现。在uboot中大量的命令交互，这些命令都对应其参数、help、和命令处理函数。通常做法去解析参数名称后遍历每一个命令名称,选择执行。也就是通常的switch、case做法。uboot给出了不一样的操作。先看uboot怎么做。定义命令处理函数；cmd/version.c#L17static int do_version(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { char buf[DISPLAY_OPTIONS_BANNER_LENGTH]; printf(display_options_get_banner(false, buf, sizeof(buf))); #ifdef CC_VERSION_STRING puts(CC_VERSION_STRING "\n"); #endif #ifdef LD_VERSION_STRING puts(LD_VERSION_STRING "\n"); #endif #ifdef CONFIG_SYS_COREBOOT printf("coreboot-%s (%s)\n", lib_sysinfo.version, lib_sysinfo.build); #endif return 0; }申明该命令；cmd/version.c#L35U_BOOT_CMD( version, 1, 1, do_version, "print monitor, compiler and linker version", "" );之后对于对应console 键入命令version就能够查找到该命令处理函数 do_version 执行。接下来，剖析背后细节。对于do_version通过U_BOOT_CMD完成了函数申明。include/command.h#L368#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)include/command.h#L325#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(struct cmd_tbl, _name, cmd) = \ U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp);include/linker_lists.h#L70#define ll_entry_declare(_type, _name, _list) \ _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_"#_list"_2_"#_name)))include/command.h#L353#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, \ _help, _comp) \ { #_name, _maxargs, NULL, 0 ? _cmd : NULL, _usage, \ _CMD_HELP(_help) _CMD_COMPLETE(_comp) }include/command.h#L237#ifdef CONFIG_AUTO_COMPLETE # define _CMD_COMPLETE(x) x, #else # define _CMD_COMPLETE(x) #endif #ifdef CONFIG_SYS_LONGHELP # define _CMD_HELP(x) x, #else # define _CMD_HELP(x) #endif最终U_BOOT_CMD申明定义了结构体类型为cmd_tbl 的变量_u_boot_list_2_cmd_2_version 并且完成了该命令的初始化赋值。struct cmd_tbl _u_boot_list_2_cmd_2_version __aligned(4) __attribute__((unused, section(".u_boot_list_2_""cmd""_2_""version"))) = { "version", 1, NULL, 0 ? do_version : NULL, "print monitor, compiler and linker version", };提示：对于如上宏展开，可以直接复制代码到文件，直接通过gcc -E cmd.c -o cmd.i 预处理展开操作。对于如上的变量属性操作__aligned、__attribute__参考6.35 Specifying Attributes of Types。__attribute__（4）表示改结构体变量4字节对齐，__attribute__声明该变量属性可能不会被使用，同时存储区为.u_boot_list_2_cmd_2_version。接下来梳理命令如何查找并执行。common/command.c#L607 /* Look up command in command table */ cmdtp = find_cmd(argv[0]); if (cmdtp == NULL) { printf("Unknown command '%s' - try 'help'\n", argv[0]); return 1; }common/command.c#L122struct cmd_tbl *find_cmd(const char *cmd) { struct cmd_tbl *start = ll_entry_start(struct cmd_tbl, cmd); const int len = ll_entry_count(struct cmd_tbl, cmd); return find_cmd_tbl(cmd, start, len); }对于命令查找，首先是通过ll_entry_start、ll_entry_count 找到命令集合的首地址和数量，然后再通过参数名依次匹配。那么ll_entry_start和ll_entry_count然后如何确定所有命令集合的首地址和数量的呢？还是尝试把宏展开。如上宏展开后。struct cmd_tbl *start = ({ static char start[0] __aligned(4) __attribute__((unused, section(".u_boot_list_2_""cmd""_1"))); (struct cmd_tbl *) &start; }); const int len = ({ struct cmd_tbl *start = ({ static char start[0] __aligned(4) __attribute__((unused, section(".u_boot_list_2_""cmd""_1"))); (struct cmd_tbl *) &start; }); struct cmd_tbl *end = ({ static char end[0] __aligned(4) __attribute__((unused, section(".u_boot_list_2_""cmd""_3"))); (struct cmd_tbl *) &end; }); unsigned int _ll_result = end - start; _ll_result; }); return find_cmd_tbl(cmd, start, len);不难看出，其直接定义局部变量start、end 取变量属性为4字节对齐，存储区域为.u_boot_list_2_cmd_1和.u_boot_list_2_cmd_3地址，从而获取到所有命令集合的首地址和结束地址。这里仍然有疑问。如何保证.u_boot_list_2_cmd_2_xxx 存储顺序是线性的？原来在链接参数中会做添加申明：README.commands#L130 .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); }提示：将如上链接选项添加文件之后通过-T参数指定该链接脚本。通过编译生成的map文件我们再次确认如上存储映射关系。.u_boot_list 0x4a0a640c 0x6b8 *(SORT(.u_boot_list*)) //顺序存储。 .u_boot_list_2_cmd_1 0x4a0a640c 0x0 common/built-in.o .u_boot_list_2_cmd_2_base 0x4a0a640c 0x1c common/built-in.o 0x4a0a640c _u_boot_list_2_cmd_2_base .u_boot_list_2_cmd_2_boot 0x4a0a6428 0x1c common/built-in.o 0x4a0a6428 _u_boot_list_2_cmd_2_boot .u_boot_list_2_cmd_2_boota 0x4a0a6444 0x1c common/built-in.o 0x4a0a6444 _u_boot_list_2_cmd_2_boota //...... .u_boot_list_2_cmd_2_version 0x4a0a6a80 0x1c common/built-in.o 0x4a0a6a80 _u_boot_list_2_cmd_2_version .u_boot_list_2_cmd_3 0x4a0a6a9c 0x0 common/built-in.o 0x4a0a6a9c . = ALIGN (0x4) //4字节对齐。提示：通过链接选项-Wl,-Map=u-boot.map 生成map文件。也可以通过readelf -a 读取。每次调用ll_entry_start 都会定义变量start，并且存储在.u_boot_list_2_cmd_1 会覆盖？这里不是定义存储在.u_boot_list_2_cmd_1结构体变量，而是定义指针类型，取该存储地址，而对于。.u_boot_list_2_cmd_1和.u_boot_list_2_cmd_3为什么能够取其命令列表的首尾地址？现在还没有找到如上链接参数*(SORT(.u_boot_list*)) ，详细说明文档，但是不难猜测这里采用了通配符u_boot_list* 匹配了所有定义在该存储区域的变量和指针类型，并且按照递增数字排序，因为.u_boot_list_2_cmd_1 是该存储区域的指针类型，按照该数字排序.u_boot_list_2_cmd_1取到了第一个存储变量首地址，相同地.u_boot_list_2_cmd_3取到了该存储区域的最后一个结构体变量尾地址。最后我们总结如上操作的优势：对比switch case更具灵活性，定义完处理函数后，直接在任意位置声明即可；对比将所有命令参数添加至链表动态注册，可以节省一部分内存，同时也不用统一去省去注册；有了如上编程技巧，对于基于事件驱动编程实现会更加方便。
how to use vscode debug elf ?elf 是linux 下可执行可链接文件（executable and linkable format）。通过gcc/make 我们很容易编译出我们需要的elf文件，借助vscode可以在ubuntu图形化调试该程序，再也不用在gdb命令操作了。如下是elf文件结构，包含文件头（elf header）和代码、数据区索引表（program header table、section header table）和对应代码区、数据区。明白了如上文件结构，还需要具体解答如下疑惑。什么文件可以被调试？通过file命令对比可以知道用以调试的文件包含 with debug_info。$file release_bin release_bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b057c6d9ce31addd0db181d567845dbc1fe69013, for GNU/Linux 3.2.0, not stripped可以被调试的文件。$file debug_bin debug_bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3c0501fa666e6fb8fdabad385772ed37053fca6f, for GNU/Linux 3.2.0, with debug_info, not strippeddebug 版本额外包含什么信息？$readelf -a debug_bin |grep debug [30] .debug_aranges PROGBITS 0000000000000000 000c1ba4 [31] .debug_info PROGBITS 0000000000000000 000c9e44 [32] .debug_abbrev PROGBITS 0000000000000000 0014b504 [33] .debug_line PROGBITS 0000000000000000 001591d9 [34] .debug_str PROGBITS 0000000000000000 001b50e3 [35] .debug_ranges PROGBITS 0000000000000000 00206cb0 [36] .debug_macro PROGBITS 0000000000000000 0020f540如上可调式的elf文件中增加包含 debug_aranges、debug_info等7个section存储包含数组、文件信息、行数、字符串、宏等调试信息。可以通过 --debug-dump=aranges/info/abbrev/line/str/ranges/macro分别显示对应的 区信息。$readelf debug_bin --debug-dump=info|head -n 20 Contents of the .debug_info section: Compilation Unit @ offset 0x0: Length: 0x1027 (32-bit) Version: 4 Abbrev Offset: 0x0 Pointer Size: 8 <0>: Abbrev Number: 1 (DW_TAG_compile_unit) <c> DW_AT_producer : (indirect string, offset: 0x5537): GNU C99 9.2.1 20191008 -mtune=generic -march=x86-64 -g3 -O0 -std=gnu99 -ffunction-sections -fdata-sections -fcommon -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection <10> DW_AT_language : 12 (ANSI C99) <11> DW_AT_name : (indirect string, offset: 0x3ba4): app_main.c <15> DW_AT_comp_dir : (indirect string, offset: 0xd2c3): /home/lijie/my_project/debug_bin <19> DW_AT_ranges : 0x30 <1d> DW_AT_low_pc : 0x0 <25> DW_AT_stmt_list : 0x0 <29> DW_AT_GNU_macros : 0x0 <1><2d>: Abbrev Number: 2 (DW_TAG_base_type) <2e> DW_AT_byte_size : 1 <2f> DW_AT_encoding : 8 (unsigned char) <30> DW_AT_name : (indirect string, offset: 0x8fe1): unsigned char如何设置debug/release 版本？知道了用以调试的程序信息，那么如何编译对应程序的debug版本，通常来说编gcc的 -gdebug 选项和-O优化选项：if DEBUG AM_CFLAGS = -g3 -O0 #调试的最高等级，优化的最低的等级； AM_CXXFLAGS = -g3 -O0 else AM_CFLAGS = -O2 #优化的的最高等级； AM_CXXFLAGS = -O2 endif提示：如上命令参数详情可以参考3.10 Options for Debugging Your Program、3.11 Options That Control Optimization。有了如上elf文件认识，接下来就是vscode debug程序了，成功编译可执行文件后，通过vscode 建立工程添加所有源文件。Extensions（Ctrl+Shift+x）->添加c/c++ 插件（其包含用以调试的gdb）；Run(Ctrl+Shift+D)->create a launch.json file. ->C++ (GDB/LLDB)，在自动生成的launch.json 找到program文件输入包含调试信息的elf文件路径即可。提示：如下的program路径可以是绝对路径，也可以是相对于工程目录变量${workspaceFolder} 的相对目录。{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/debug_bin", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }成功配置该路径后保存后Run(Ctrl+Shift+D)->选择（gdb）Launch，绿色按钮进程调试。troubleshootingcannot set breakpointModule containing this breakpoint has not yet loaded or the breakpoint address could not be obtained调试过程中不能设置断点，参考如上描述，编译该程序过程中增加-g3 -O0选项使能调试信息，禁止优化选项。