ctf-string.c revision 1.1.1.3 1 1.1 christos /* CTF string table management.
2 1.1.1.3 christos Copyright (C) 2019-2024 Free Software Foundation, Inc.
3 1.1 christos
4 1.1 christos This file is part of libctf.
5 1.1 christos
6 1.1 christos libctf is free software; you can redistribute it and/or modify it under
7 1.1 christos the terms of the GNU General Public License as published by the Free
8 1.1 christos Software Foundation; either version 3, or (at your option) any later
9 1.1 christos version.
10 1.1 christos
11 1.1 christos This program is distributed in the hope that it will be useful, but
12 1.1 christos WITHOUT ANY WARRANTY; without even the implied warranty of
13 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 1.1 christos See the GNU General Public License for more details.
15 1.1 christos
16 1.1 christos You should have received a copy of the GNU General Public License
17 1.1 christos along with this program; see the file COPYING. If not see
18 1.1 christos <http://www.gnu.org/licenses/>. */
19 1.1 christos
20 1.1.1.3 christos #include <assert.h>
21 1.1 christos #include <ctf-impl.h>
22 1.1 christos #include <string.h>
23 1.1 christos
24 1.1.1.3 christos static ctf_str_atom_t *
25 1.1.1.3 christos ctf_str_add_ref_internal (ctf_dict_t *fp, const char *str,
26 1.1.1.3 christos int flags, uint32_t *ref);
27 1.1.1.3 christos
28 1.1.1.3 christos /* Convert an encoded CTF string name into a pointer to a C string, possibly
29 1.1.1.3 christos using an explicit internal provisional strtab rather than the fp-based
30 1.1.1.3 christos one. */
31 1.1 christos const char *
32 1.1.1.2 christos ctf_strraw_explicit (ctf_dict_t *fp, uint32_t name, ctf_strs_t *strtab)
33 1.1 christos {
34 1.1 christos ctf_strs_t *ctsp = &fp->ctf_str[CTF_NAME_STID (name)];
35 1.1 christos
36 1.1 christos if ((CTF_NAME_STID (name) == CTF_STRTAB_0) && (strtab != NULL))
37 1.1 christos ctsp = strtab;
38 1.1 christos
39 1.1.1.3 christos /* If this name is in the external strtab, and there is a synthetic
40 1.1.1.3 christos strtab, use it in preference. (This is used to add the set of strings
41 1.1.1.3 christos -- symbol names, etc -- the linker knows about before the strtab is
42 1.1.1.3 christos written out.) */
43 1.1 christos
44 1.1 christos if (CTF_NAME_STID (name) == CTF_STRTAB_1
45 1.1 christos && fp->ctf_syn_ext_strtab != NULL)
46 1.1 christos return ctf_dynhash_lookup (fp->ctf_syn_ext_strtab,
47 1.1 christos (void *) (uintptr_t) name);
48 1.1 christos
49 1.1.1.3 christos /* If the name is in the internal strtab, and the name offset is beyond
50 1.1.1.3 christos the end of the ctsp->cts_len but below the ctf_str_prov_offset, this is
51 1.1.1.3 christos a provisional string added by ctf_str_add*() but not yet built into a
52 1.1.1.3 christos real strtab: get the value out of the ctf_prov_strtab. */
53 1.1 christos
54 1.1 christos if (CTF_NAME_STID (name) == CTF_STRTAB_0
55 1.1 christos && name >= ctsp->cts_len && name < fp->ctf_str_prov_offset)
56 1.1 christos return ctf_dynhash_lookup (fp->ctf_prov_strtab,
57 1.1 christos (void *) (uintptr_t) name);
58 1.1 christos
59 1.1 christos if (ctsp->cts_strs != NULL && CTF_NAME_OFFSET (name) < ctsp->cts_len)
60 1.1 christos return (ctsp->cts_strs + CTF_NAME_OFFSET (name));
61 1.1 christos
62 1.1 christos /* String table not loaded or corrupt offset. */
63 1.1 christos return NULL;
64 1.1 christos }
65 1.1 christos
66 1.1 christos /* Convert an encoded CTF string name into a pointer to a C string by looking
67 1.1 christos up the appropriate string table buffer and then adding the offset. */
68 1.1 christos const char *
69 1.1.1.2 christos ctf_strraw (ctf_dict_t *fp, uint32_t name)
70 1.1 christos {
71 1.1 christos return ctf_strraw_explicit (fp, name, NULL);
72 1.1 christos }
73 1.1 christos
74 1.1 christos /* Return a guaranteed-non-NULL pointer to the string with the given CTF
75 1.1 christos name. */
76 1.1 christos const char *
77 1.1.1.2 christos ctf_strptr (ctf_dict_t *fp, uint32_t name)
78 1.1 christos {
79 1.1 christos const char *s = ctf_strraw (fp, name);
80 1.1 christos return (s != NULL ? s : "(?)");
81 1.1 christos }
82 1.1 christos
83 1.1.1.3 christos /* As above, but return info on what is wrong in more detail.
84 1.1.1.3 christos (Used for type lookups.) */
85 1.1.1.3 christos
86 1.1.1.3 christos const char *
87 1.1.1.3 christos ctf_strptr_validate (ctf_dict_t *fp, uint32_t name)
88 1.1.1.3 christos {
89 1.1.1.3 christos const char *str = ctf_strraw (fp, name);
90 1.1.1.3 christos
91 1.1.1.3 christos if (str == NULL)
92 1.1.1.3 christos {
93 1.1.1.3 christos if (CTF_NAME_STID (name) == CTF_STRTAB_1
94 1.1.1.3 christos && fp->ctf_syn_ext_strtab == NULL
95 1.1.1.3 christos && fp->ctf_str[CTF_NAME_STID (name)].cts_strs == NULL)
96 1.1.1.3 christos {
97 1.1.1.3 christos ctf_set_errno (fp, ECTF_STRTAB);
98 1.1.1.3 christos return NULL;
99 1.1.1.3 christos }
100 1.1.1.3 christos
101 1.1.1.3 christos ctf_set_errno (fp, ECTF_BADNAME);
102 1.1.1.3 christos return NULL;
103 1.1.1.3 christos }
104 1.1.1.3 christos return str;
105 1.1.1.3 christos }
106 1.1.1.3 christos
107 1.1 christos /* Remove all refs to a given atom. */
108 1.1 christos static void
109 1.1 christos ctf_str_purge_atom_refs (ctf_str_atom_t *atom)
110 1.1 christos {
111 1.1 christos ctf_str_atom_ref_t *ref, *next;
112 1.1 christos
113 1.1 christos for (ref = ctf_list_next (&atom->csa_refs); ref != NULL; ref = next)
114 1.1 christos {
115 1.1 christos next = ctf_list_next (ref);
116 1.1 christos ctf_list_delete (&atom->csa_refs, ref);
117 1.1.1.3 christos if (atom->csa_flags & CTF_STR_ATOM_MOVABLE)
118 1.1.1.3 christos {
119 1.1.1.3 christos ctf_str_atom_ref_movable_t *movref;
120 1.1.1.3 christos movref = (ctf_str_atom_ref_movable_t *) ref;
121 1.1.1.3 christos ctf_dynhash_remove (movref->caf_movable_refs, ref);
122 1.1.1.3 christos }
123 1.1.1.3 christos
124 1.1 christos free (ref);
125 1.1 christos }
126 1.1 christos }
127 1.1 christos
128 1.1.1.3 christos /* Free an atom. */
129 1.1 christos static void
130 1.1 christos ctf_str_free_atom (void *a)
131 1.1 christos {
132 1.1 christos ctf_str_atom_t *atom = a;
133 1.1 christos
134 1.1 christos ctf_str_purge_atom_refs (atom);
135 1.1.1.3 christos
136 1.1.1.3 christos if (atom->csa_flags & CTF_STR_ATOM_FREEABLE)
137 1.1.1.3 christos free (atom->csa_str);
138 1.1.1.3 christos
139 1.1 christos free (atom);
140 1.1 christos }
141 1.1 christos
142 1.1 christos /* Create the atoms table. There is always at least one atom in it, the null
143 1.1.1.3 christos string: but also pull in atoms from the internal strtab. (We rely on
144 1.1.1.3 christos calls to ctf_str_add_external to populate external strtab entries, since
145 1.1.1.3 christos these are often not quite the same as what appears in any external
146 1.1.1.3 christos strtab, and the external strtab is often huge and best not aggressively
147 1.1.1.3 christos pulled in.) */
148 1.1 christos int
149 1.1.1.2 christos ctf_str_create_atoms (ctf_dict_t *fp)
150 1.1 christos {
151 1.1.1.3 christos size_t i;
152 1.1.1.3 christos
153 1.1 christos fp->ctf_str_atoms = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
154 1.1.1.3 christos NULL, ctf_str_free_atom);
155 1.1.1.2 christos if (!fp->ctf_str_atoms)
156 1.1 christos return -ENOMEM;
157 1.1 christos
158 1.1 christos if (!fp->ctf_prov_strtab)
159 1.1 christos fp->ctf_prov_strtab = ctf_dynhash_create (ctf_hash_integer,
160 1.1 christos ctf_hash_eq_integer,
161 1.1 christos NULL, NULL);
162 1.1 christos if (!fp->ctf_prov_strtab)
163 1.1 christos goto oom_prov_strtab;
164 1.1 christos
165 1.1.1.3 christos fp->ctf_str_movable_refs = ctf_dynhash_create (ctf_hash_integer,
166 1.1.1.3 christos ctf_hash_eq_integer,
167 1.1.1.3 christos NULL, NULL);
168 1.1.1.3 christos if (!fp->ctf_str_movable_refs)
169 1.1.1.3 christos goto oom_movable_refs;
170 1.1.1.2 christos
171 1.1 christos errno = 0;
172 1.1 christos ctf_str_add (fp, "");
173 1.1 christos if (errno == ENOMEM)
174 1.1 christos goto oom_str_add;
175 1.1 christos
176 1.1.1.3 christos /* Pull in all the strings in the strtab as new atoms. The provisional
177 1.1.1.3 christos strtab must be empty at this point, so there is no need to populate
178 1.1.1.3 christos atoms from it as well. Types in this subset are frozen and readonly,
179 1.1.1.3 christos so the refs list and movable refs list need not be populated. */
180 1.1.1.3 christos
181 1.1.1.3 christos for (i = 0; i < fp->ctf_str[CTF_STRTAB_0].cts_len;
182 1.1.1.3 christos i += strlen (&fp->ctf_str[CTF_STRTAB_0].cts_strs[i]) + 1)
183 1.1.1.3 christos {
184 1.1.1.3 christos ctf_str_atom_t *atom;
185 1.1.1.3 christos
186 1.1.1.3 christos if (fp->ctf_str[CTF_STRTAB_0].cts_strs[i] == 0)
187 1.1.1.3 christos continue;
188 1.1.1.3 christos
189 1.1.1.3 christos atom = ctf_str_add_ref_internal (fp, &fp->ctf_str[CTF_STRTAB_0].cts_strs[i],
190 1.1.1.3 christos 0, 0);
191 1.1.1.3 christos
192 1.1.1.3 christos if (!atom)
193 1.1.1.3 christos goto oom_str_add;
194 1.1.1.3 christos
195 1.1.1.3 christos atom->csa_offset = i;
196 1.1.1.3 christos }
197 1.1.1.3 christos
198 1.1 christos return 0;
199 1.1 christos
200 1.1 christos oom_str_add:
201 1.1.1.3 christos ctf_dynhash_destroy (fp->ctf_str_movable_refs);
202 1.1.1.3 christos fp->ctf_str_movable_refs = NULL;
203 1.1.1.3 christos oom_movable_refs:
204 1.1 christos ctf_dynhash_destroy (fp->ctf_prov_strtab);
205 1.1 christos fp->ctf_prov_strtab = NULL;
206 1.1 christos oom_prov_strtab:
207 1.1 christos ctf_dynhash_destroy (fp->ctf_str_atoms);
208 1.1 christos fp->ctf_str_atoms = NULL;
209 1.1 christos return -ENOMEM;
210 1.1 christos }
211 1.1 christos
212 1.1.1.3 christos /* Destroy the atoms table and associated refs. */
213 1.1 christos void
214 1.1.1.2 christos ctf_str_free_atoms (ctf_dict_t *fp)
215 1.1 christos {
216 1.1 christos ctf_dynhash_destroy (fp->ctf_prov_strtab);
217 1.1 christos ctf_dynhash_destroy (fp->ctf_str_atoms);
218 1.1.1.3 christos ctf_dynhash_destroy (fp->ctf_str_movable_refs);
219 1.1.1.3 christos if (fp->ctf_dynstrtab)
220 1.1.1.3 christos {
221 1.1.1.3 christos free (fp->ctf_dynstrtab->cts_strs);
222 1.1.1.3 christos free (fp->ctf_dynstrtab);
223 1.1.1.3 christos }
224 1.1 christos }
225 1.1 christos
226 1.1.1.2 christos #define CTF_STR_ADD_REF 0x1
227 1.1.1.3 christos #define CTF_STR_PROVISIONAL 0x2
228 1.1.1.3 christos #define CTF_STR_MOVABLE 0x4
229 1.1.1.3 christos
230 1.1.1.3 christos /* Allocate a ref and bind it into a ref list. */
231 1.1.1.3 christos
232 1.1.1.3 christos static ctf_str_atom_ref_t *
233 1.1.1.3 christos aref_create (ctf_dict_t *fp, ctf_str_atom_t *atom, uint32_t *ref, int flags)
234 1.1.1.3 christos {
235 1.1.1.3 christos ctf_str_atom_ref_t *aref;
236 1.1.1.3 christos size_t s = sizeof (struct ctf_str_atom_ref);
237 1.1.1.3 christos
238 1.1.1.3 christos if (flags & CTF_STR_MOVABLE)
239 1.1.1.3 christos s = sizeof (struct ctf_str_atom_ref_movable);
240 1.1.1.3 christos
241 1.1.1.3 christos aref = malloc (s);
242 1.1.1.3 christos
243 1.1.1.3 christos if (!aref)
244 1.1.1.3 christos return NULL;
245 1.1.1.3 christos
246 1.1.1.3 christos aref->caf_ref = ref;
247 1.1.1.3 christos
248 1.1.1.3 christos /* Movable refs get a backpointer to them in ctf_str_movable_refs, and a
249 1.1.1.3 christos pointer to ctf_str_movable_refs itself in the ref, for use when freeing
250 1.1.1.3 christos refs: they can be moved later in batches via a call to
251 1.1.1.3 christos ctf_str_move_refs. */
252 1.1.1.3 christos
253 1.1.1.3 christos if (flags & CTF_STR_MOVABLE)
254 1.1.1.3 christos {
255 1.1.1.3 christos ctf_str_atom_ref_movable_t *movref = (ctf_str_atom_ref_movable_t *) aref;
256 1.1.1.3 christos
257 1.1.1.3 christos movref->caf_movable_refs = fp->ctf_str_movable_refs;
258 1.1.1.3 christos
259 1.1.1.3 christos if (ctf_dynhash_insert (fp->ctf_str_movable_refs, ref, aref) < 0)
260 1.1.1.3 christos {
261 1.1.1.3 christos free (aref);
262 1.1.1.3 christos return NULL;
263 1.1.1.3 christos }
264 1.1.1.3 christos }
265 1.1.1.3 christos
266 1.1.1.3 christos ctf_list_append (&atom->csa_refs, aref);
267 1.1.1.3 christos
268 1.1.1.3 christos return aref;
269 1.1.1.3 christos }
270 1.1.1.3 christos
271 1.1.1.3 christos /* Add a string to the atoms table, copying the passed-in string if
272 1.1.1.3 christos necessary. Return the atom added. Return NULL only when out of memory
273 1.1.1.3 christos (and do not touch the passed-in string in that case).
274 1.1.1.3 christos
275 1.1.1.3 christos Possibly add a provisional entry for this string to the provisional
276 1.1.1.3 christos strtab. If the string is in the provisional strtab, update its ref list
277 1.1.1.3 christos with the passed-in ref, causing the ref to be updated when the strtab is
278 1.1.1.3 christos written out. */
279 1.1.1.2 christos
280 1.1 christos static ctf_str_atom_t *
281 1.1.1.2 christos ctf_str_add_ref_internal (ctf_dict_t *fp, const char *str,
282 1.1.1.2 christos int flags, uint32_t *ref)
283 1.1 christos {
284 1.1 christos char *newstr = NULL;
285 1.1 christos ctf_str_atom_t *atom = NULL;
286 1.1.1.3 christos int added = 0;
287 1.1 christos
288 1.1 christos atom = ctf_dynhash_lookup (fp->ctf_str_atoms, str);
289 1.1 christos
290 1.1.1.3 christos /* Existing atoms get refs added only if they are provisional:
291 1.1.1.3 christos non-provisional strings already have a fixed strtab offset, and just
292 1.1.1.3 christos get their ref updated immediately, since its value cannot change. */
293 1.1 christos
294 1.1 christos if (atom)
295 1.1 christos {
296 1.1.1.3 christos if (!ctf_dynhash_lookup (fp->ctf_prov_strtab, (void *) (uintptr_t)
297 1.1.1.3 christos atom->csa_offset))
298 1.1.1.3 christos {
299 1.1.1.3 christos if (flags & CTF_STR_ADD_REF)
300 1.1.1.3 christos {
301 1.1.1.3 christos if (atom->csa_external_offset)
302 1.1.1.3 christos *ref = atom->csa_external_offset;
303 1.1.1.3 christos else
304 1.1.1.3 christos *ref = atom->csa_offset;
305 1.1.1.3 christos }
306 1.1.1.3 christos return atom;
307 1.1.1.3 christos }
308 1.1.1.3 christos
309 1.1.1.2 christos if (flags & CTF_STR_ADD_REF)
310 1.1 christos {
311 1.1.1.3 christos if (!aref_create (fp, atom, ref, flags))
312 1.1.1.3 christos {
313 1.1.1.3 christos ctf_set_errno (fp, ENOMEM);
314 1.1.1.3 christos return NULL;
315 1.1.1.3 christos }
316 1.1 christos }
317 1.1.1.3 christos
318 1.1 christos return atom;
319 1.1 christos }
320 1.1 christos
321 1.1.1.3 christos /* New atom. */
322 1.1.1.3 christos
323 1.1 christos if ((atom = malloc (sizeof (struct ctf_str_atom))) == NULL)
324 1.1 christos goto oom;
325 1.1 christos memset (atom, 0, sizeof (struct ctf_str_atom));
326 1.1 christos
327 1.1.1.3 christos /* Don't allocate new strings if this string is within an mmapped
328 1.1.1.3 christos strtab. */
329 1.1.1.3 christos
330 1.1.1.3 christos if ((unsigned char *) str < (unsigned char *) fp->ctf_data_mmapped
331 1.1.1.3 christos || (unsigned char *) str > (unsigned char *) fp->ctf_data_mmapped + fp->ctf_data_mmapped_len)
332 1.1.1.3 christos {
333 1.1.1.3 christos if ((newstr = strdup (str)) == NULL)
334 1.1.1.3 christos goto oom;
335 1.1.1.3 christos atom->csa_flags |= CTF_STR_ATOM_FREEABLE;
336 1.1.1.3 christos atom->csa_str = newstr;
337 1.1.1.3 christos }
338 1.1.1.3 christos else
339 1.1.1.3 christos atom->csa_str = (char *) str;
340 1.1 christos
341 1.1.1.3 christos if (ctf_dynhash_insert (fp->ctf_str_atoms, atom->csa_str, atom) < 0)
342 1.1 christos goto oom;
343 1.1.1.3 christos added = 1;
344 1.1 christos
345 1.1 christos atom->csa_snapshot_id = fp->ctf_snapshots;
346 1.1 christos
347 1.1.1.3 christos /* New atoms marked provisional go into the provisional strtab, and get a
348 1.1.1.3 christos ref added. */
349 1.1.1.3 christos
350 1.1.1.3 christos if (flags & CTF_STR_PROVISIONAL)
351 1.1 christos {
352 1.1 christos atom->csa_offset = fp->ctf_str_prov_offset;
353 1.1 christos
354 1.1 christos if (ctf_dynhash_insert (fp->ctf_prov_strtab, (void *) (uintptr_t)
355 1.1 christos atom->csa_offset, (void *) atom->csa_str) < 0)
356 1.1 christos goto oom;
357 1.1 christos
358 1.1 christos fp->ctf_str_prov_offset += strlen (atom->csa_str) + 1;
359 1.1 christos
360 1.1.1.3 christos if (flags & CTF_STR_ADD_REF)
361 1.1.1.3 christos {
362 1.1.1.3 christos if (!aref_create (fp, atom, ref, flags))
363 1.1.1.3 christos goto oom;
364 1.1.1.3 christos }
365 1.1 christos }
366 1.1.1.3 christos
367 1.1 christos return atom;
368 1.1 christos
369 1.1 christos oom:
370 1.1.1.3 christos if (added)
371 1.1.1.3 christos ctf_dynhash_remove (fp->ctf_str_atoms, atom->csa_str);
372 1.1 christos free (atom);
373 1.1 christos free (newstr);
374 1.1.1.2 christos ctf_set_errno (fp, ENOMEM);
375 1.1 christos return NULL;
376 1.1 christos }
377 1.1 christos
378 1.1 christos /* Add a string to the atoms table, without augmenting the ref list for this
379 1.1 christos string: return a 'provisional offset' which can be used to return this string
380 1.1 christos until ctf_str_write_strtab is called, or 0 on failure. (Everywhere the
381 1.1 christos provisional offset is assigned to should be added as a ref using
382 1.1 christos ctf_str_add_ref() as well.) */
383 1.1 christos uint32_t
384 1.1.1.2 christos ctf_str_add (ctf_dict_t *fp, const char *str)
385 1.1 christos {
386 1.1 christos ctf_str_atom_t *atom;
387 1.1.1.2 christos
388 1.1 christos if (!str)
389 1.1.1.2 christos str = "";
390 1.1 christos
391 1.1.1.3 christos atom = ctf_str_add_ref_internal (fp, str, CTF_STR_PROVISIONAL, 0);
392 1.1 christos if (!atom)
393 1.1 christos return 0;
394 1.1 christos
395 1.1 christos return atom->csa_offset;
396 1.1 christos }
397 1.1 christos
398 1.1 christos /* Like ctf_str_add(), but additionally augment the atom's refs list with the
399 1.1 christos passed-in ref, whether or not the string is already present. There is no
400 1.1 christos attempt to deduplicate the refs list (but duplicates are harmless). */
401 1.1 christos uint32_t
402 1.1.1.2 christos ctf_str_add_ref (ctf_dict_t *fp, const char *str, uint32_t *ref)
403 1.1 christos {
404 1.1 christos ctf_str_atom_t *atom;
405 1.1.1.2 christos
406 1.1 christos if (!str)
407 1.1.1.2 christos str = "";
408 1.1.1.2 christos
409 1.1.1.2 christos atom = ctf_str_add_ref_internal (fp, str, CTF_STR_ADD_REF
410 1.1.1.3 christos | CTF_STR_PROVISIONAL, ref);
411 1.1.1.2 christos if (!atom)
412 1.1 christos return 0;
413 1.1 christos
414 1.1.1.2 christos return atom->csa_offset;
415 1.1.1.2 christos }
416 1.1.1.2 christos
417 1.1.1.3 christos /* Like ctf_str_add_ref(), but note that the ref may be moved later on. */
418 1.1.1.2 christos uint32_t
419 1.1.1.3 christos ctf_str_add_movable_ref (ctf_dict_t *fp, const char *str, uint32_t *ref)
420 1.1.1.2 christos {
421 1.1.1.2 christos ctf_str_atom_t *atom;
422 1.1.1.2 christos
423 1.1.1.2 christos if (!str)
424 1.1.1.2 christos str = "";
425 1.1.1.2 christos
426 1.1.1.3 christos atom = ctf_str_add_ref_internal (fp, str, CTF_STR_ADD_REF
427 1.1.1.3 christos | CTF_STR_PROVISIONAL
428 1.1.1.3 christos | CTF_STR_MOVABLE, ref);
429 1.1 christos if (!atom)
430 1.1 christos return 0;
431 1.1 christos
432 1.1 christos return atom->csa_offset;
433 1.1 christos }
434 1.1 christos
435 1.1 christos /* Add an external strtab reference at OFFSET. Returns zero if the addition
436 1.1 christos failed, nonzero otherwise. */
437 1.1 christos int
438 1.1.1.2 christos ctf_str_add_external (ctf_dict_t *fp, const char *str, uint32_t offset)
439 1.1 christos {
440 1.1 christos ctf_str_atom_t *atom;
441 1.1.1.2 christos
442 1.1 christos if (!str)
443 1.1.1.2 christos str = "";
444 1.1 christos
445 1.1.1.2 christos atom = ctf_str_add_ref_internal (fp, str, 0, 0);
446 1.1 christos if (!atom)
447 1.1 christos return 0;
448 1.1 christos
449 1.1 christos atom->csa_external_offset = CTF_SET_STID (offset, CTF_STRTAB_1);
450 1.1.1.2 christos
451 1.1.1.2 christos if (!fp->ctf_syn_ext_strtab)
452 1.1.1.2 christos fp->ctf_syn_ext_strtab = ctf_dynhash_create (ctf_hash_integer,
453 1.1.1.2 christos ctf_hash_eq_integer,
454 1.1.1.2 christos NULL, NULL);
455 1.1.1.2 christos if (!fp->ctf_syn_ext_strtab)
456 1.1.1.2 christos {
457 1.1.1.2 christos ctf_set_errno (fp, ENOMEM);
458 1.1.1.2 christos return 0;
459 1.1.1.2 christos }
460 1.1.1.2 christos
461 1.1.1.2 christos if (ctf_dynhash_insert (fp->ctf_syn_ext_strtab,
462 1.1.1.2 christos (void *) (uintptr_t)
463 1.1.1.2 christos atom->csa_external_offset,
464 1.1.1.2 christos (void *) atom->csa_str) < 0)
465 1.1.1.2 christos {
466 1.1.1.2 christos /* No need to bother freeing the syn_ext_strtab: it will get freed at
467 1.1.1.2 christos ctf_str_write_strtab time if unreferenced. */
468 1.1.1.2 christos ctf_set_errno (fp, ENOMEM);
469 1.1.1.2 christos return 0;
470 1.1.1.2 christos }
471 1.1.1.2 christos
472 1.1 christos return 1;
473 1.1 christos }
474 1.1 christos
475 1.1.1.3 christos /* Note that refs have moved from (SRC, LEN) to DEST. We use the movable
476 1.1.1.3 christos refs backpointer for this, because it is done an amortized-constant
477 1.1.1.3 christos number of times during structure member and enumerand addition, and if we
478 1.1.1.3 christos did a linear search this would turn such addition into an O(n^2)
479 1.1.1.3 christos operation. Even this is not linear, but it's better than that. */
480 1.1.1.3 christos int
481 1.1.1.3 christos ctf_str_move_refs (ctf_dict_t *fp, void *src, size_t len, void *dest)
482 1.1.1.3 christos {
483 1.1.1.3 christos uintptr_t p;
484 1.1.1.3 christos
485 1.1.1.3 christos if (src == dest)
486 1.1.1.3 christos return 0;
487 1.1.1.3 christos
488 1.1.1.3 christos for (p = (uintptr_t) src; p - (uintptr_t) src < len; p++)
489 1.1.1.3 christos {
490 1.1.1.3 christos ctf_str_atom_ref_t *ref;
491 1.1.1.3 christos
492 1.1.1.3 christos if ((ref = ctf_dynhash_lookup (fp->ctf_str_movable_refs,
493 1.1.1.3 christos (ctf_str_atom_ref_t *) p)) != NULL)
494 1.1.1.3 christos {
495 1.1.1.3 christos int out_of_memory;
496 1.1.1.3 christos
497 1.1.1.3 christos ref->caf_ref = (uint32_t *) (((uintptr_t) ref->caf_ref +
498 1.1.1.3 christos (uintptr_t) dest - (uintptr_t) src));
499 1.1.1.3 christos ctf_dynhash_remove (fp->ctf_str_movable_refs,
500 1.1.1.3 christos (ctf_str_atom_ref_t *) p);
501 1.1.1.3 christos out_of_memory = ctf_dynhash_insert (fp->ctf_str_movable_refs,
502 1.1.1.3 christos ref->caf_ref, ref);
503 1.1.1.3 christos assert (out_of_memory == 0);
504 1.1.1.3 christos }
505 1.1.1.3 christos }
506 1.1.1.3 christos
507 1.1.1.3 christos return 0;
508 1.1.1.3 christos }
509 1.1.1.3 christos
510 1.1 christos /* Remove a single ref. */
511 1.1 christos void
512 1.1.1.2 christos ctf_str_remove_ref (ctf_dict_t *fp, const char *str, uint32_t *ref)
513 1.1 christos {
514 1.1 christos ctf_str_atom_ref_t *aref, *anext;
515 1.1 christos ctf_str_atom_t *atom = NULL;
516 1.1 christos
517 1.1 christos atom = ctf_dynhash_lookup (fp->ctf_str_atoms, str);
518 1.1 christos if (!atom)
519 1.1 christos return;
520 1.1 christos
521 1.1 christos for (aref = ctf_list_next (&atom->csa_refs); aref != NULL; aref = anext)
522 1.1 christos {
523 1.1 christos anext = ctf_list_next (aref);
524 1.1 christos if (aref->caf_ref == ref)
525 1.1 christos {
526 1.1 christos ctf_list_delete (&atom->csa_refs, aref);
527 1.1 christos free (aref);
528 1.1 christos }
529 1.1 christos }
530 1.1 christos }
531 1.1 christos
532 1.1 christos /* A ctf_dynhash_iter_remove() callback that removes atoms later than a given
533 1.1.1.2 christos snapshot ID. External atoms are never removed, because they came from the
534 1.1.1.2 christos linker string table and are still present even if you roll back type
535 1.1.1.2 christos additions. */
536 1.1 christos static int
537 1.1 christos ctf_str_rollback_atom (void *key _libctf_unused_, void *value, void *arg)
538 1.1 christos {
539 1.1 christos ctf_str_atom_t *atom = (ctf_str_atom_t *) value;
540 1.1 christos ctf_snapshot_id_t *id = (ctf_snapshot_id_t *) arg;
541 1.1 christos
542 1.1.1.2 christos return (atom->csa_snapshot_id > id->snapshot_id)
543 1.1.1.2 christos && (atom->csa_external_offset == 0);
544 1.1 christos }
545 1.1 christos
546 1.1.1.2 christos /* Roll back, deleting all (internal) atoms created after a particular ID. */
547 1.1 christos void
548 1.1.1.2 christos ctf_str_rollback (ctf_dict_t *fp, ctf_snapshot_id_t id)
549 1.1 christos {
550 1.1 christos ctf_dynhash_iter_remove (fp->ctf_str_atoms, ctf_str_rollback_atom, &id);
551 1.1 christos }
552 1.1 christos
553 1.1 christos /* An adaptor around ctf_purge_atom_refs. */
554 1.1 christos static void
555 1.1 christos ctf_str_purge_one_atom_refs (void *key _libctf_unused_, void *value,
556 1.1 christos void *arg _libctf_unused_)
557 1.1 christos {
558 1.1 christos ctf_str_atom_t *atom = (ctf_str_atom_t *) value;
559 1.1 christos ctf_str_purge_atom_refs (atom);
560 1.1 christos }
561 1.1 christos
562 1.1 christos /* Remove all the recorded refs from the atoms table. */
563 1.1 christos void
564 1.1.1.2 christos ctf_str_purge_refs (ctf_dict_t *fp)
565 1.1 christos {
566 1.1.1.3 christos ctf_dynhash_iter (fp->ctf_str_atoms, ctf_str_purge_one_atom_refs, NULL);
567 1.1 christos }
568 1.1 christos
569 1.1 christos /* Update a list of refs to the specified value. */
570 1.1 christos static void
571 1.1 christos ctf_str_update_refs (ctf_str_atom_t *refs, uint32_t value)
572 1.1 christos {
573 1.1 christos ctf_str_atom_ref_t *ref;
574 1.1 christos
575 1.1 christos for (ref = ctf_list_next (&refs->csa_refs); ref != NULL;
576 1.1 christos ref = ctf_list_next (ref))
577 1.1.1.3 christos *(ref->caf_ref) = value;
578 1.1 christos }
579 1.1 christos
580 1.1 christos /* Sort the strtab. */
581 1.1 christos static int
582 1.1 christos ctf_str_sort_strtab (const void *a, const void *b)
583 1.1 christos {
584 1.1 christos ctf_str_atom_t **one = (ctf_str_atom_t **) a;
585 1.1 christos ctf_str_atom_t **two = (ctf_str_atom_t **) b;
586 1.1 christos
587 1.1 christos return (strcmp ((*one)->csa_str, (*two)->csa_str));
588 1.1 christos }
589 1.1 christos
590 1.1 christos /* Write out and return a strtab containing all strings with recorded refs,
591 1.1.1.3 christos adjusting the refs to refer to the corresponding string. The returned
592 1.1.1.3 christos strtab is already assigned to strtab 0 in this dict, is owned by this
593 1.1.1.3 christos dict, and may be NULL on error. Also populate the synthetic strtab with
594 1.1.1.3 christos mappings from external strtab offsets to names, so we can look them up
595 1.1.1.3 christos with ctf_strptr(). Only external strtab offsets with references are
596 1.1.1.3 christos added.
597 1.1.1.3 christos
598 1.1.1.3 christos As a side effect, replaces the strtab of the current dict with the newly-
599 1.1.1.3 christos generated strtab. This is an exception to the general rule that
600 1.1.1.3 christos serialization does not change the dict passed in, because the alternative
601 1.1.1.3 christos is to copy the entire atoms table on every reserialization just to avoid
602 1.1.1.3 christos modifying the original, which is excessively costly for minimal gain.
603 1.1.1.3 christos
604 1.1.1.3 christos We use the lazy man's approach and double memory costs by always storing
605 1.1.1.3 christos atoms as individually allocated entities whenever they come from anywhere
606 1.1.1.3 christos but a freshly-opened, mmapped dict, even though after serialization there
607 1.1.1.3 christos is another copy in the strtab; this ensures that ctf_strptr()-returned
608 1.1.1.3 christos pointers to them remain valid for the lifetime of the dict.
609 1.1.1.3 christos
610 1.1.1.3 christos This is all rendered more complex because if a dict is ctf_open()ed it
611 1.1.1.3 christos will have a bunch of strings in its strtab already, and their strtab
612 1.1.1.3 christos offsets can never change (without piles of complexity to rescan the
613 1.1.1.3 christos entire dict just to get all the offsets to all of them into the atoms
614 1.1.1.3 christos table). Entries below the existing strtab limit are just copied into the
615 1.1.1.3 christos new dict: entries above it are new, and are are sorted first, then
616 1.1.1.3 christos appended to it. The sorting is purely a compression-efficiency
617 1.1.1.3 christos improvement, and we get nearly as good an improvement from sorting big
618 1.1.1.3 christos chunks like this as we would from sorting the whole thing. */
619 1.1.1.3 christos
620 1.1.1.3 christos const ctf_strs_writable_t *
621 1.1.1.2 christos ctf_str_write_strtab (ctf_dict_t *fp)
622 1.1 christos {
623 1.1.1.3 christos ctf_strs_writable_t *strtab;
624 1.1.1.3 christos size_t strtab_count = 0;
625 1.1 christos uint32_t cur_stroff = 0;
626 1.1 christos ctf_str_atom_t **sorttab;
627 1.1.1.3 christos ctf_next_t *it = NULL;
628 1.1 christos size_t i;
629 1.1.1.3 christos void *v;
630 1.1.1.3 christos int err;
631 1.1.1.3 christos int new_strtab = 0;
632 1.1 christos int any_external = 0;
633 1.1 christos
634 1.1.1.3 christos strtab = calloc (1, sizeof (ctf_strs_writable_t));
635 1.1.1.3 christos if (!strtab)
636 1.1.1.3 christos return NULL;
637 1.1.1.3 christos
638 1.1.1.3 christos /* The strtab contains the existing string table at its start: figure out
639 1.1.1.3 christos how many new strings we need to add. We only need to add new strings
640 1.1.1.3 christos that have no external offset, that have refs, and that are found in the
641 1.1.1.3 christos provisional strtab. If the existing strtab is empty we also need to
642 1.1.1.3 christos add the null string at its start. */
643 1.1 christos
644 1.1.1.3 christos strtab->cts_len = fp->ctf_str[CTF_STRTAB_0].cts_len;
645 1.1.1.3 christos
646 1.1.1.3 christos if (strtab->cts_len == 0)
647 1.1 christos {
648 1.1.1.3 christos new_strtab = 1;
649 1.1.1.3 christos strtab->cts_len++; /* For the \0. */
650 1.1 christos }
651 1.1 christos
652 1.1.1.3 christos /* Count new entries in the strtab: i.e. entries in the provisional
653 1.1.1.3 christos strtab. Ignore any entry for \0, entries which ended up in the
654 1.1.1.3 christos external strtab, and unreferenced entries. */
655 1.1 christos
656 1.1.1.3 christos while ((err = ctf_dynhash_next (fp->ctf_prov_strtab, &it, NULL, &v)) == 0)
657 1.1.1.3 christos {
658 1.1.1.3 christos const char *str = (const char *) v;
659 1.1.1.3 christos ctf_str_atom_t *atom;
660 1.1 christos
661 1.1.1.3 christos atom = ctf_dynhash_lookup (fp->ctf_str_atoms, str);
662 1.1.1.3 christos if (!ctf_assert (fp, atom))
663 1.1.1.3 christos goto err_strtab;
664 1.1.1.3 christos
665 1.1.1.3 christos if (atom->csa_str[0] == 0 || ctf_list_empty_p (&atom->csa_refs) ||
666 1.1.1.3 christos atom->csa_external_offset)
667 1.1.1.3 christos continue;
668 1.1.1.3 christos
669 1.1.1.3 christos strtab->cts_len += strlen (atom->csa_str) + 1;
670 1.1.1.3 christos strtab_count++;
671 1.1.1.3 christos }
672 1.1.1.3 christos if (err != ECTF_NEXT_END)
673 1.1.1.3 christos {
674 1.1.1.3 christos ctf_dprintf ("ctf_str_write_strtab: error counting strtab entries: %s\n",
675 1.1.1.3 christos ctf_errmsg (err));
676 1.1.1.3 christos goto err_strtab;
677 1.1.1.3 christos }
678 1.1.1.3 christos
679 1.1.1.3 christos ctf_dprintf ("%lu bytes of strings in strtab: %lu pre-existing.\n",
680 1.1.1.3 christos (unsigned long) strtab->cts_len,
681 1.1.1.3 christos (unsigned long) fp->ctf_str[CTF_STRTAB_0].cts_len);
682 1.1.1.3 christos
683 1.1.1.3 christos /* Sort the new part of the strtab. */
684 1.1.1.3 christos
685 1.1.1.3 christos sorttab = calloc (strtab_count, sizeof (ctf_str_atom_t *));
686 1.1 christos if (!sorttab)
687 1.1.1.3 christos {
688 1.1.1.3 christos ctf_set_errno (fp, ENOMEM);
689 1.1.1.3 christos goto err_strtab;
690 1.1.1.3 christos }
691 1.1.1.3 christos
692 1.1.1.3 christos i = 0;
693 1.1.1.3 christos while ((err = ctf_dynhash_next (fp->ctf_prov_strtab, &it, NULL, &v)) == 0)
694 1.1.1.3 christos {
695 1.1.1.3 christos ctf_str_atom_t *atom;
696 1.1 christos
697 1.1.1.3 christos atom = ctf_dynhash_lookup (fp->ctf_str_atoms, v);
698 1.1.1.3 christos if (!ctf_assert (fp, atom))
699 1.1.1.3 christos goto err_sorttab;
700 1.1.1.3 christos
701 1.1.1.3 christos if (atom->csa_str[0] == 0 || ctf_list_empty_p (&atom->csa_refs) ||
702 1.1.1.3 christos atom->csa_external_offset)
703 1.1.1.3 christos continue;
704 1.1.1.3 christos
705 1.1.1.3 christos sorttab[i++] = atom;
706 1.1.1.3 christos }
707 1.1 christos
708 1.1.1.3 christos qsort (sorttab, strtab_count, sizeof (ctf_str_atom_t *),
709 1.1 christos ctf_str_sort_strtab);
710 1.1 christos
711 1.1.1.3 christos if ((strtab->cts_strs = malloc (strtab->cts_len)) == NULL)
712 1.1.1.3 christos goto err_sorttab;
713 1.1 christos
714 1.1.1.3 christos cur_stroff = fp->ctf_str[CTF_STRTAB_0].cts_len;
715 1.1.1.3 christos
716 1.1.1.3 christos if (new_strtab)
717 1.1 christos {
718 1.1.1.3 christos strtab->cts_strs[0] = 0;
719 1.1.1.3 christos cur_stroff++;
720 1.1.1.3 christos }
721 1.1.1.3 christos else
722 1.1.1.3 christos memcpy (strtab->cts_strs, fp->ctf_str[CTF_STRTAB_0].cts_strs,
723 1.1.1.3 christos fp->ctf_str[CTF_STRTAB_0].cts_len);
724 1.1.1.3 christos
725 1.1.1.3 christos /* Work over the sorttab, add its strings to the strtab, and remember
726 1.1.1.3 christos where they are in the csa_offset for the appropriate atom. No ref
727 1.1.1.3 christos updating is done at this point, because refs might well relate to
728 1.1.1.3 christos already-existing strings, or external strings, which do not need adding
729 1.1.1.3 christos to the strtab and may not be in the sorttab. */
730 1.1.1.3 christos
731 1.1.1.3 christos for (i = 0; i < strtab_count; i++)
732 1.1.1.3 christos {
733 1.1.1.3 christos sorttab[i]->csa_offset = cur_stroff;
734 1.1.1.3 christos strcpy (&strtab->cts_strs[cur_stroff], sorttab[i]->csa_str);
735 1.1.1.3 christos cur_stroff += strlen (sorttab[i]->csa_str) + 1;
736 1.1.1.3 christos }
737 1.1.1.3 christos free (sorttab);
738 1.1.1.3 christos sorttab = NULL;
739 1.1.1.3 christos
740 1.1.1.3 christos /* Update all refs, then purge them as no longer necessary: also update
741 1.1.1.3 christos the strtab appropriately. */
742 1.1.1.3 christos
743 1.1.1.3 christos while ((err = ctf_dynhash_next (fp->ctf_str_atoms, &it, NULL, &v)) == 0)
744 1.1.1.3 christos {
745 1.1.1.3 christos ctf_str_atom_t *atom = (ctf_str_atom_t *) v;
746 1.1.1.3 christos uint32_t offset;
747 1.1 christos
748 1.1.1.3 christos if (ctf_list_empty_p (&atom->csa_refs))
749 1.1.1.3 christos continue;
750 1.1.1.3 christos
751 1.1.1.3 christos if (atom->csa_external_offset)
752 1.1.1.3 christos {
753 1.1 christos any_external = 1;
754 1.1.1.3 christos offset = atom->csa_external_offset;
755 1.1 christos }
756 1.1 christos else
757 1.1.1.3 christos offset = atom->csa_offset;
758 1.1.1.3 christos ctf_str_update_refs (atom, offset);
759 1.1 christos }
760 1.1.1.3 christos if (err != ECTF_NEXT_END)
761 1.1.1.3 christos {
762 1.1.1.3 christos ctf_dprintf ("ctf_str_write_strtab: error iterating over atoms while updating refs: %s\n",
763 1.1.1.3 christos ctf_errmsg (err));
764 1.1.1.3 christos goto err_strtab;
765 1.1.1.3 christos }
766 1.1.1.3 christos ctf_str_purge_refs (fp);
767 1.1 christos
768 1.1 christos if (!any_external)
769 1.1 christos {
770 1.1 christos ctf_dynhash_destroy (fp->ctf_syn_ext_strtab);
771 1.1 christos fp->ctf_syn_ext_strtab = NULL;
772 1.1 christos }
773 1.1 christos
774 1.1.1.3 christos /* Replace the old strtab with the new one in this dict. */
775 1.1.1.3 christos
776 1.1.1.3 christos if (fp->ctf_dynstrtab)
777 1.1.1.3 christos {
778 1.1.1.3 christos free (fp->ctf_dynstrtab->cts_strs);
779 1.1.1.3 christos free (fp->ctf_dynstrtab);
780 1.1.1.3 christos }
781 1.1.1.3 christos
782 1.1.1.3 christos fp->ctf_dynstrtab = strtab;
783 1.1.1.3 christos fp->ctf_str[CTF_STRTAB_0].cts_strs = strtab->cts_strs;
784 1.1.1.3 christos fp->ctf_str[CTF_STRTAB_0].cts_len = strtab->cts_len;
785 1.1.1.3 christos
786 1.1 christos /* All the provisional strtab entries are now real strtab entries, and
787 1.1 christos ctf_strptr() will find them there. The provisional offset now starts right
788 1.1 christos beyond the new end of the strtab. */
789 1.1 christos
790 1.1 christos ctf_dynhash_empty (fp->ctf_prov_strtab);
791 1.1.1.3 christos fp->ctf_str_prov_offset = strtab->cts_len + 1;
792 1.1 christos return strtab;
793 1.1 christos
794 1.1.1.3 christos err_sorttab:
795 1.1 christos free (sorttab);
796 1.1.1.3 christos err_strtab:
797 1.1.1.3 christos free (strtab);
798 1.1.1.3 christos return NULL;
799 1.1 christos }
800