Skip to main content

ghostscope_platform/
tls.rs

1//! Linux x86_64 TLS layout helpers.
2
3use std::fmt;
4use std::path::Path;
5
6const BTF_KIND_INT: u32 = 1;
7const BTF_KIND_PTR: u32 = 2;
8const BTF_KIND_ARRAY: u32 = 3;
9const BTF_KIND_STRUCT: u32 = 4;
10const BTF_KIND_UNION: u32 = 5;
11const BTF_KIND_ENUM: u32 = 6;
12const BTF_KIND_FWD: u32 = 7;
13const BTF_KIND_TYPEDEF: u32 = 8;
14const BTF_KIND_VOLATILE: u32 = 9;
15const BTF_KIND_CONST: u32 = 10;
16const BTF_KIND_RESTRICT: u32 = 11;
17const BTF_KIND_FUNC: u32 = 12;
18const BTF_KIND_FUNC_PROTO: u32 = 13;
19const BTF_KIND_VAR: u32 = 14;
20const BTF_KIND_DATASEC: u32 = 15;
21const BTF_KIND_FLOAT: u32 = 16;
22const BTF_KIND_DECL_TAG: u32 = 17;
23const BTF_KIND_TYPE_TAG: u32 = 18;
24const BTF_KIND_ENUM64: u32 = 19;
25
26const PT_TLS: u32 = 7;
27const EM_X86_64: u16 = 62;
28
29#[derive(Debug)]
30pub enum TlsLayoutError {
31    Io(std::io::Error),
32    Invalid(&'static str),
33    Missing(&'static str),
34}
35
36impl fmt::Display for TlsLayoutError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            Self::Io(err) => write!(f, "{err}"),
40            Self::Invalid(detail) => write!(f, "invalid TLS layout data: {detail}"),
41            Self::Missing(detail) => write!(f, "missing TLS layout data: {detail}"),
42        }
43    }
44}
45
46impl std::error::Error for TlsLayoutError {}
47
48impl From<std::io::Error> for TlsLayoutError {
49    fn from(err: std::io::Error) -> Self {
50        Self::Io(err)
51    }
52}
53
54#[derive(Debug)]
55struct BtfRecord {
56    name: String,
57    kind: u32,
58    members: Vec<BtfMember>,
59}
60
61#[derive(Debug)]
62struct BtfMember {
63    name: String,
64    bit_offset: u32,
65}
66
67/// Return the byte offset of `task_struct.thread.fsbase` in the running kernel.
68pub fn current_task_fsbase_offset() -> Result<u64, TlsLayoutError> {
69    let data = std::fs::read("/sys/kernel/btf/vmlinux")?;
70    let records = parse_btf_records(&data)?;
71    let task_thread_bits = find_member_bit_offset(&records, "task_struct", "thread")?;
72    let thread_fsbase_bits = find_member_bit_offset(&records, "thread_struct", "fsbase")?;
73
74    if task_thread_bits % 8 != 0 || thread_fsbase_bits % 8 != 0 {
75        return Err(TlsLayoutError::Invalid(
76            "task_struct.thread.fsbase is not byte-aligned",
77        ));
78    }
79
80    Ok(u64::from(task_thread_bits / 8 + thread_fsbase_bits / 8))
81}
82
83/// Return the x86_64 static TLS bias from thread pointer for an ELF module.
84///
85/// For the initial-exec/local-exec TLS model used by the main executable on
86/// x86_64 variant-II TLS, the DWARF TLS offset is relative to the module TLS
87/// image, while the runtime address is `fsbase - aligned_tls_size + offset`.
88pub fn static_tls_bias_for_elf(path: &Path) -> Result<Option<i64>, TlsLayoutError> {
89    let data = std::fs::read(path)?;
90    if data.len() < 64 || &data[0..4] != b"\x7fELF" {
91        return Err(TlsLayoutError::Invalid("not an ELF file"));
92    }
93    if data[4] != 2 || data[5] != 1 {
94        return Err(TlsLayoutError::Invalid(
95            "only little-endian ELF64 is supported for TLS layout",
96        ));
97    }
98    if read_u16(&data, 18)? != EM_X86_64 {
99        return Err(TlsLayoutError::Invalid(
100            "only x86_64 ELF TLS layout is supported",
101        ));
102    }
103
104    let phoff = read_u64(&data, 32)? as usize;
105    let phentsize = read_u16(&data, 54)? as usize;
106    let phnum = read_u16(&data, 56)? as usize;
107    if phentsize < 56 {
108        return Err(TlsLayoutError::Invalid(
109            "ELF program header entry too small",
110        ));
111    }
112
113    for index in 0..phnum {
114        let offset =
115            phoff
116                .checked_add(index.saturating_mul(phentsize))
117                .ok_or(TlsLayoutError::Invalid(
118                    "ELF program header offset overflow",
119                ))?;
120        if offset + phentsize > data.len() {
121            return Err(TlsLayoutError::Invalid("ELF program header out of bounds"));
122        }
123        if read_u32(&data, offset)? != PT_TLS {
124            continue;
125        }
126
127        let memsz = read_u64(&data, offset + 40)?;
128        let align = read_u64(&data, offset + 48)?.max(1);
129        let aligned = round_up(memsz, align)?;
130        if aligned > i64::MAX as u64 {
131            return Err(TlsLayoutError::Invalid("TLS segment too large"));
132        }
133        return Ok(Some(-(aligned as i64)));
134    }
135
136    Ok(None)
137}
138
139fn parse_btf_records(data: &[u8]) -> Result<Vec<Option<BtfRecord>>, TlsLayoutError> {
140    if data.len() < 24 {
141        return Err(TlsLayoutError::Invalid("BTF header too small"));
142    }
143    if read_u16(data, 0)? != 0xeb9f {
144        return Err(TlsLayoutError::Invalid(
145            "only little-endian BTF is supported",
146        ));
147    }
148
149    let hdr_len = read_u32(data, 4)? as usize;
150    let type_off = read_u32(data, 8)? as usize;
151    let type_len = read_u32(data, 12)? as usize;
152    let str_off = read_u32(data, 16)? as usize;
153    let str_len = read_u32(data, 20)? as usize;
154    let type_start = checked_range_start(hdr_len, type_off)?;
155    let type_end = checked_range_end(type_start, type_len, data.len())?;
156    let str_start = checked_range_start(hdr_len, str_off)?;
157    let str_end = checked_range_end(str_start, str_len, data.len())?;
158    let strings = &data[str_start..str_end];
159
160    let mut offset = type_start;
161    let mut records = vec![None];
162    while offset < type_end {
163        if offset + 12 > type_end {
164            return Err(TlsLayoutError::Invalid("truncated BTF type header"));
165        }
166        let name_offset = read_u32(data, offset)?;
167        let info = read_u32(data, offset + 4)?;
168        let kind = (info >> 24) & 0x1f;
169        let vlen = (info & 0xffff) as usize;
170        let kflag = (info & 0x8000_0000) != 0;
171        offset += 12;
172
173        let name = read_btf_string(strings, name_offset)?;
174        let mut members = Vec::new();
175        let extra_size = match kind {
176            BTF_KIND_STRUCT | BTF_KIND_UNION => {
177                for _ in 0..vlen {
178                    if offset + 12 > type_end {
179                        return Err(TlsLayoutError::Invalid("truncated BTF member"));
180                    }
181                    let member_name_offset = read_u32(data, offset)?;
182                    let raw_offset = read_u32(data, offset + 8)?;
183                    let bit_offset = if kflag {
184                        raw_offset & 0x00ff_ffff
185                    } else {
186                        raw_offset
187                    };
188                    members.push(BtfMember {
189                        name: read_btf_string(strings, member_name_offset)?,
190                        bit_offset,
191                    });
192                    offset += 12;
193                }
194                0
195            }
196            BTF_KIND_INT | BTF_KIND_VAR | BTF_KIND_DECL_TAG => 4,
197            BTF_KIND_ARRAY => 12,
198            BTF_KIND_ENUM | BTF_KIND_FUNC_PROTO => vlen.saturating_mul(8),
199            BTF_KIND_DATASEC | BTF_KIND_ENUM64 => vlen.saturating_mul(12),
200            BTF_KIND_PTR | BTF_KIND_FWD | BTF_KIND_TYPEDEF | BTF_KIND_VOLATILE | BTF_KIND_CONST
201            | BTF_KIND_RESTRICT | BTF_KIND_FUNC | BTF_KIND_FLOAT | BTF_KIND_TYPE_TAG => 0,
202            _ => return Err(TlsLayoutError::Invalid("unknown BTF kind")),
203        };
204
205        if extra_size != 0 {
206            offset = offset
207                .checked_add(extra_size)
208                .ok_or(TlsLayoutError::Invalid("BTF type offset overflow"))?;
209            if offset > type_end {
210                return Err(TlsLayoutError::Invalid("truncated BTF type payload"));
211            }
212        }
213
214        records.push(Some(BtfRecord {
215            name,
216            kind,
217            members,
218        }));
219    }
220
221    Ok(records)
222}
223
224fn find_member_bit_offset(
225    records: &[Option<BtfRecord>],
226    struct_name: &'static str,
227    member_name: &'static str,
228) -> Result<u32, TlsLayoutError> {
229    let record = records
230        .iter()
231        .flatten()
232        .find(|record| record.kind == BTF_KIND_STRUCT && record.name == struct_name)
233        .ok_or(TlsLayoutError::Missing(struct_name))?;
234
235    record
236        .members
237        .iter()
238        .find(|member| member.name == member_name)
239        .map(|member| member.bit_offset)
240        .ok_or(TlsLayoutError::Missing(member_name))
241}
242
243fn read_btf_string(strings: &[u8], offset: u32) -> Result<String, TlsLayoutError> {
244    let start = offset as usize;
245    if start >= strings.len() {
246        return Err(TlsLayoutError::Invalid("BTF string offset out of bounds"));
247    }
248    let tail = &strings[start..];
249    let nul = tail
250        .iter()
251        .position(|byte| *byte == 0)
252        .ok_or(TlsLayoutError::Invalid("unterminated BTF string"))?;
253    Ok(String::from_utf8_lossy(&tail[..nul]).into_owned())
254}
255
256fn checked_range_start(base: usize, offset: usize) -> Result<usize, TlsLayoutError> {
257    base.checked_add(offset)
258        .ok_or(TlsLayoutError::Invalid("range start overflow"))
259}
260
261fn checked_range_end(start: usize, len: usize, data_len: usize) -> Result<usize, TlsLayoutError> {
262    let end = start
263        .checked_add(len)
264        .ok_or(TlsLayoutError::Invalid("range end overflow"))?;
265    if end > data_len {
266        return Err(TlsLayoutError::Invalid("range out of bounds"));
267    }
268    Ok(end)
269}
270
271fn round_up(value: u64, align: u64) -> Result<u64, TlsLayoutError> {
272    if align <= 1 {
273        return Ok(value);
274    }
275    let addend = align - 1;
276    let rounded = value
277        .checked_add(addend)
278        .ok_or(TlsLayoutError::Invalid("TLS alignment overflow"))?
279        / align
280        * align;
281    Ok(rounded)
282}
283
284fn read_u16(data: &[u8], offset: usize) -> Result<u16, TlsLayoutError> {
285    let bytes = data
286        .get(offset..offset + 2)
287        .ok_or(TlsLayoutError::Invalid("u16 read out of bounds"))?;
288    Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
289}
290
291fn read_u32(data: &[u8], offset: usize) -> Result<u32, TlsLayoutError> {
292    let bytes = data
293        .get(offset..offset + 4)
294        .ok_or(TlsLayoutError::Invalid("u32 read out of bounds"))?;
295    Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
296}
297
298fn read_u64(data: &[u8], offset: usize) -> Result<u64, TlsLayoutError> {
299    let bytes = data
300        .get(offset..offset + 8)
301        .ok_or(TlsLayoutError::Invalid("u64 read out of bounds"))?;
302    Ok(u64::from_le_bytes([
303        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
304    ]))
305}