1 /* Relocate debug information.
2 Copyright (C) 2005, 2006, 2007, 2008 Red Hat, Inc.
3 This file is part of Red Hat elfutils.
4
5 Red Hat elfutils is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by the
7 Free Software Foundation; version 2 of the License.
8
9 Red Hat elfutils is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with Red Hat elfutils; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
17
18 In addition, as a special exception, Red Hat, Inc. gives You the
19 additional right to link the code of Red Hat elfutils with code licensed
20 under any Open Source Initiative certified open source license
21 (http://www.opensource.org/licenses/index.php) which requires the
22 distribution of source code with any binary distribution and to
23 distribute linked combinations of the two. Non-GPL Code permitted under
24 this exception must only link to the code of Red Hat elfutils through
25 those well defined interfaces identified in the file named EXCEPTION
26 found in the source code files (the "Approved Interfaces"). The files
27 of Non-GPL Code may instantiate templates or use macros or inline
28 functions from the Approved Interfaces without causing the resulting
29 work to be covered by the GNU General Public License. Only Red Hat,
30 Inc. may make changes or additions to the list of Approved Interfaces.
31 Red Hat's grant of this exception is conditioned upon your not adding
32 any new exceptions. If you wish to add a new Approved Interface or
33 exception, please contact Red Hat. You must obey the GNU General Public
34 License in all respects for all of the Red Hat elfutils code and other
35 code used in conjunction with Red Hat elfutils except the Non-GPL Code
36 covered by this exception. If you modify this file, you may extend this
37 exception to your version of the file, but you are not obligated to do
38 so. If you do not wish to provide this exception without modification,
39 you must delete this exception statement from your version and license
40 this file solely under the GPL without exception.
41
42 Red Hat elfutils is an included package of the Open Invention Network.
43 An included package of the Open Invention Network is a package for which
44 Open Invention Network licensees cross-license their patents. No patent
45 license is granted, either expressly or impliedly, by designation as an
46 included package. Should you wish to participate in the Open Invention
47 Network licensing program, please visit www.openinventionnetwork.com
48 <http://www.openinventionnetwork.com>. */
49
50 #include "libdwflP.h"
51
52 typedef uint8_t GElf_Byte;
53
54 /* Adjust *VALUE to add the load address of the SHNDX section.
55 We update the section header in place to cache the result. */
56
57 Dwfl_Error
58 internal_function
__libdwfl_relocate_value(Dwfl_Module * mod,Elf * elf,size_t * shstrndx,Elf32_Word shndx,GElf_Addr * value)59 __libdwfl_relocate_value (Dwfl_Module *mod, Elf *elf, size_t *shstrndx,
60 Elf32_Word shndx, GElf_Addr *value)
61 {
62 Elf_Scn *refscn = elf_getscn (elf, shndx);
63 GElf_Shdr refshdr_mem, *refshdr = gelf_getshdr (refscn, &refshdr_mem);
64 if (refshdr == NULL)
65 return DWFL_E_LIBELF;
66
67 if (refshdr->sh_addr == 0 && (refshdr->sh_flags & SHF_ALLOC))
68 {
69 /* This is a loaded section. Find its actual
70 address and update the section header. */
71
72 if (*shstrndx == SHN_UNDEF
73 && unlikely (elf_getshstrndx (elf, shstrndx) < 0))
74 return DWFL_E_LIBELF;
75
76 const char *name = elf_strptr (elf, *shstrndx, refshdr->sh_name);
77 if (unlikely (name == NULL))
78 return DWFL_E_LIBELF;
79
80 if ((*mod->dwfl->callbacks->section_address) (MODCB_ARGS (mod),
81 name, shndx, refshdr,
82 &refshdr->sh_addr))
83 return CBFAIL;
84
85 if (refshdr->sh_addr == (Dwarf_Addr) -1l)
86 /* The callback indicated this section wasn't really loaded but we
87 don't really care. */
88 refshdr->sh_addr = 0; /* Make no adjustment below. */
89
90 /* Update the in-core file's section header to show the final
91 load address (or unloadedness). This serves as a cache,
92 so we won't get here again for the same section. */
93 if (likely (refshdr->sh_addr != 0)
94 && unlikely (! gelf_update_shdr (refscn, refshdr)))
95 return DWFL_E_LIBELF;
96 }
97
98 /* Apply the adjustment. */
99 *value += refshdr->sh_addr;
100 return DWFL_E_NOERROR;
101 }
102
103
104 /* Cache used by relocate_getsym. */
105 struct reloc_symtab_cache
106 {
107 Elf *symelf;
108 Elf_Data *symdata;
109 Elf_Data *symxndxdata;
110 Elf_Data *symstrdata;
111 size_t symshstrndx;
112 size_t strtabndx;
113 };
114 #define RELOC_SYMTAB_CACHE(cache) \
115 struct reloc_symtab_cache cache = \
116 { NULL, NULL, NULL, NULL, SHN_UNDEF, SHN_UNDEF }
117
118 /* This is just doing dwfl_module_getsym, except that we must always use
119 the symbol table in RELOCATED itself when it has one, not MOD->symfile. */
120 static Dwfl_Error
relocate_getsym(Dwfl_Module * mod,Elf * relocated,struct reloc_symtab_cache * cache,int symndx,GElf_Sym * sym,GElf_Word * shndx)121 relocate_getsym (Dwfl_Module *mod,
122 Elf *relocated, struct reloc_symtab_cache *cache,
123 int symndx, GElf_Sym *sym, GElf_Word *shndx)
124 {
125 if (cache->symdata == NULL)
126 {
127 if (mod->symfile == NULL || mod->symfile->elf != relocated)
128 {
129 /* We have to look up the symbol table in the file we are
130 relocating, if it has its own. These reloc sections refer to
131 the symbol table in this file, and a symbol table in the main
132 file might not match. However, some tools did produce ET_REL
133 .debug files with relocs but no symtab of their own. */
134 Elf_Scn *scn = NULL;
135 while ((scn = elf_nextscn (relocated, scn)) != NULL)
136 {
137 GElf_Shdr shdr_mem, *shdr = gelf_getshdr (scn, &shdr_mem);
138 if (shdr != NULL)
139 switch (shdr->sh_type)
140 {
141 default:
142 continue;
143 case SHT_SYMTAB:
144 cache->symelf = relocated;
145 cache->symdata = elf_getdata (scn, NULL);
146 cache->strtabndx = shdr->sh_link;
147 if (unlikely (cache->symdata == NULL))
148 return DWFL_E_LIBELF;
149 break;
150 case SHT_SYMTAB_SHNDX:
151 cache->symxndxdata = elf_getdata (scn, NULL);
152 if (unlikely (cache->symxndxdata == NULL))
153 return DWFL_E_LIBELF;
154 break;
155 }
156 if (cache->symdata != NULL && cache->symxndxdata != NULL)
157 break;
158 }
159 }
160 if (cache->symdata == NULL)
161 {
162 /* We might not have looked for a symbol table file yet,
163 when coming from __libdwfl_relocate_section. */
164 if (unlikely (mod->symfile == NULL)
165 && unlikely (INTUSE(dwfl_module_getsymtab) (mod) < 0))
166 return dwfl_errno ();
167
168 /* The symbol table we have already cached is the one from
169 the file being relocated, so it's what we need. Or else
170 this is an ET_REL .debug file with no .symtab of its own;
171 the symbols refer to the section indices in the main file. */
172 cache->symelf = mod->symfile->elf;
173 cache->symdata = mod->symdata;
174 cache->symxndxdata = mod->symxndxdata;
175 cache->symstrdata = mod->symstrdata;
176 }
177 }
178
179 if (unlikely (gelf_getsymshndx (cache->symdata, cache->symxndxdata,
180 symndx, sym, shndx) == NULL))
181 return DWFL_E_LIBELF;
182
183 if (sym->st_shndx != SHN_XINDEX)
184 *shndx = sym->st_shndx;
185
186 switch (*shndx)
187 {
188 case SHN_ABS:
189 case SHN_UNDEF:
190 case SHN_COMMON:
191 return DWFL_E_NOERROR;
192 }
193
194 return __libdwfl_relocate_value (mod, cache->symelf, &cache->symshstrndx,
195 *shndx, &sym->st_value);
196 }
197
198 /* Handle an undefined symbol. We really only support ET_REL for Linux
199 kernel modules, and offline archives. The behavior of the Linux module
200 loader is very simple and easy to mimic. It only matches magically
201 exported symbols, and we match any defined symbols. But we get the same
202 answer except when the module's symbols are undefined and would prevent
203 it from being loaded. */
204 static Dwfl_Error
resolve_symbol(Dwfl_Module * referer,struct reloc_symtab_cache * symtab,GElf_Sym * sym,GElf_Word shndx)205 resolve_symbol (Dwfl_Module *referer, struct reloc_symtab_cache *symtab,
206 GElf_Sym *sym, GElf_Word shndx)
207 {
208 /* First we need its name. */
209 if (sym->st_name != 0)
210 {
211 if (symtab->symstrdata == NULL)
212 {
213 /* Cache the strtab for this symtab. */
214 assert (referer->symfile == NULL
215 || referer->symfile->elf != symtab->symelf);
216 symtab->symstrdata = elf_getdata (elf_getscn (symtab->symelf,
217 symtab->strtabndx),
218 NULL);
219 if (unlikely (symtab->symstrdata == NULL))
220 return DWFL_E_LIBELF;
221 }
222 if (unlikely (sym->st_name >= symtab->symstrdata->d_size))
223 return DWFL_E_BADSTROFF;
224
225 const char *name = symtab->symstrdata->d_buf;
226 name += sym->st_name;
227
228 for (Dwfl_Module *m = referer->dwfl->modulelist; m != NULL; m = m->next)
229 if (m != referer)
230 {
231 /* Get this module's symtab.
232 If we got a fresh error reading the table, report it.
233 If we just have no symbols in this module, no harm done. */
234 if (m->symdata == NULL
235 && m->symerr == DWFL_E_NOERROR
236 && INTUSE(dwfl_module_getsymtab) (m) < 0
237 && m->symerr != DWFL_E_NO_SYMTAB)
238 return m->symerr;
239
240 for (size_t ndx = 1; ndx < m->syments; ++ndx)
241 {
242 sym = gelf_getsymshndx (m->symdata, m->symxndxdata,
243 ndx, sym, &shndx);
244 if (unlikely (sym == NULL))
245 return DWFL_E_LIBELF;
246 if (sym->st_shndx != SHN_XINDEX)
247 shndx = sym->st_shndx;
248
249 /* We are looking for a defined global symbol with a name. */
250 if (shndx == SHN_UNDEF || shndx == SHN_COMMON
251 || GELF_ST_BIND (sym->st_info) == STB_LOCAL
252 || sym->st_name == 0)
253 continue;
254
255 /* Get this candidate symbol's name. */
256 if (unlikely (sym->st_name >= m->symstrdata->d_size))
257 return DWFL_E_BADSTROFF;
258 const char *n = m->symstrdata->d_buf;
259 n += sym->st_name;
260
261 /* Does the name match? */
262 if (strcmp (name, n))
263 continue;
264
265 /* We found it! */
266 if (shndx == SHN_ABS)
267 return DWFL_E_NOERROR;
268
269 /* In an ET_REL file, the symbol table values are relative
270 to the section, not to the module's load base. */
271 size_t symshstrndx = SHN_UNDEF;
272 return __libdwfl_relocate_value (m, m->symfile->elf,
273 &symshstrndx,
274 shndx, &sym->st_value);
275 }
276 }
277 }
278
279 return DWFL_E_RELUNDEF;
280 }
281
282 static Dwfl_Error
relocate_section(Dwfl_Module * mod,Elf * relocated,const GElf_Ehdr * ehdr,size_t shstrndx,struct reloc_symtab_cache * reloc_symtab,Elf_Scn * scn,GElf_Shdr * shdr,Elf_Scn * tscn,bool debugscn,bool partial)283 relocate_section (Dwfl_Module *mod, Elf *relocated, const GElf_Ehdr *ehdr,
284 size_t shstrndx, struct reloc_symtab_cache *reloc_symtab,
285 Elf_Scn *scn, GElf_Shdr *shdr,
286 Elf_Scn *tscn, bool debugscn, bool partial)
287 {
288 /* First, fetch the name of the section these relocations apply to. */
289 GElf_Shdr tshdr_mem;
290 GElf_Shdr *tshdr = gelf_getshdr (tscn, &tshdr_mem);
291 const char *tname = elf_strptr (relocated, shstrndx, tshdr->sh_name);
292 if (tname == NULL)
293 return DWFL_E_LIBELF;
294
295 if (debugscn && ! ebl_debugscn_p (mod->ebl, tname))
296 /* This relocation section is not for a debugging section.
297 Nothing to do here. */
298 return DWFL_E_NOERROR;
299
300 /* Fetch the section data that needs the relocations applied. */
301 Elf_Data *tdata = elf_rawdata (tscn, NULL);
302 if (tdata == NULL)
303 return DWFL_E_LIBELF;
304
305 /* Apply one relocation. Returns true for any invalid data. */
306 Dwfl_Error relocate (GElf_Addr offset, const GElf_Sxword *addend,
307 int rtype, int symndx)
308 {
309 /* First see if this is a reloc we can handle.
310 If we are skipping it, don't bother resolving the symbol. */
311 Elf_Type type = ebl_reloc_simple_type (mod->ebl, rtype);
312 if (unlikely (type == ELF_T_NUM))
313 return DWFL_E_BADRELTYPE;
314
315 /* First, resolve the symbol to an absolute value. */
316 GElf_Addr value;
317
318 if (symndx == STN_UNDEF)
319 /* When strip removes a section symbol referring to a
320 section moved into the debuginfo file, it replaces
321 that symbol index in relocs with STN_UNDEF. We
322 don't actually need the symbol, because those relocs
323 are always references relative to the nonallocated
324 debugging sections, which start at zero. */
325 value = 0;
326 else
327 {
328 GElf_Sym sym;
329 GElf_Word shndx;
330 Dwfl_Error error = relocate_getsym (mod, relocated, reloc_symtab,
331 symndx, &sym, &shndx);
332 if (unlikely (error != DWFL_E_NOERROR))
333 return error;
334
335 if (shndx == SHN_UNDEF || shndx == SHN_COMMON)
336 {
337 /* Maybe we can figure it out anyway. */
338 error = resolve_symbol (mod, reloc_symtab, &sym, shndx);
339 if (error != DWFL_E_NOERROR)
340 return error;
341 }
342
343 value = sym.st_value;
344 }
345
346 /* These are the types we can relocate. */
347 #define TYPES DO_TYPE (BYTE, Byte); DO_TYPE (HALF, Half); \
348 DO_TYPE (WORD, Word); DO_TYPE (SWORD, Sword); \
349 DO_TYPE (XWORD, Xword); DO_TYPE (SXWORD, Sxword)
350 size_t size;
351 switch (type)
352 {
353 #define DO_TYPE(NAME, Name) \
354 case ELF_T_##NAME: \
355 size = sizeof (GElf_##Name); \
356 break
357 TYPES;
358 #undef DO_TYPE
359 default:
360 return DWFL_E_BADRELTYPE;
361 }
362
363 if (offset + size > tdata->d_size)
364 return DWFL_E_BADRELOFF;
365
366 #define DO_TYPE(NAME, Name) GElf_##Name Name;
367 union { TYPES; } tmpbuf;
368 #undef DO_TYPE
369 Elf_Data tmpdata =
370 {
371 .d_type = type,
372 .d_buf = &tmpbuf,
373 .d_size = size,
374 .d_version = EV_CURRENT,
375 };
376 Elf_Data rdata =
377 {
378 .d_type = type,
379 .d_buf = tdata->d_buf + offset,
380 .d_size = size,
381 .d_version = EV_CURRENT,
382 };
383
384 /* XXX check for overflow? */
385 if (addend)
386 {
387 /* For the addend form, we have the value already. */
388 value += *addend;
389 switch (type)
390 {
391 #define DO_TYPE(NAME, Name) \
392 case ELF_T_##NAME: \
393 tmpbuf.Name = value; \
394 break
395 TYPES;
396 #undef DO_TYPE
397 default:
398 abort ();
399 }
400 }
401 else
402 {
403 /* Extract the original value and apply the reloc. */
404 Elf_Data *d = gelf_xlatetom (relocated, &tmpdata, &rdata,
405 ehdr->e_ident[EI_DATA]);
406 if (d == NULL)
407 return DWFL_E_LIBELF;
408 assert (d == &tmpdata);
409 switch (type)
410 {
411 #define DO_TYPE(NAME, Name) \
412 case ELF_T_##NAME: \
413 tmpbuf.Name += (GElf_##Name) value; \
414 break
415 TYPES;
416 #undef DO_TYPE
417 default:
418 abort ();
419 }
420 }
421
422 /* Now convert the relocated datum back to the target
423 format. This will write into rdata.d_buf, which
424 points into the raw section data being relocated. */
425 Elf_Data *s = gelf_xlatetof (relocated, &rdata, &tmpdata,
426 ehdr->e_ident[EI_DATA]);
427 if (s == NULL)
428 return DWFL_E_LIBELF;
429 assert (s == &rdata);
430
431 /* We have applied this relocation! */
432 return DWFL_E_NOERROR;
433 }
434
435 /* Fetch the relocation section and apply each reloc in it. */
436 Elf_Data *reldata = elf_getdata (scn, NULL);
437 if (reldata == NULL)
438 return DWFL_E_LIBELF;
439
440 Dwfl_Error result = DWFL_E_NOERROR;
441 bool first_badreltype = true;
442 inline void check_badreltype (void)
443 {
444 if (first_badreltype)
445 {
446 first_badreltype = false;
447 if (ebl_get_elfmachine (mod->ebl) == EM_NONE)
448 /* This might be because ebl_openbackend failed to find
449 any libebl_CPU.so library. Diagnose that clearly. */
450 result = DWFL_E_UNKNOWN_MACHINE;
451 }
452 }
453
454 size_t nrels = shdr->sh_size / shdr->sh_entsize;
455 size_t complete = 0;
456 if (shdr->sh_type == SHT_REL)
457 for (size_t relidx = 0; !result && relidx < nrels; ++relidx)
458 {
459 GElf_Rel rel_mem, *r = gelf_getrel (reldata, relidx, &rel_mem);
460 if (r == NULL)
461 return DWFL_E_LIBELF;
462 result = relocate (r->r_offset, NULL,
463 GELF_R_TYPE (r->r_info),
464 GELF_R_SYM (r->r_info));
465 check_badreltype ();
466 if (partial)
467 switch (result)
468 {
469 case DWFL_E_NOERROR:
470 /* We applied the relocation. Elide it. */
471 memset (&rel_mem, 0, sizeof rel_mem);
472 gelf_update_rel (reldata, relidx, &rel_mem);
473 ++complete;
474 break;
475 case DWFL_E_BADRELTYPE:
476 case DWFL_E_RELUNDEF:
477 /* We couldn't handle this relocation. Skip it. */
478 result = DWFL_E_NOERROR;
479 break;
480 default:
481 break;
482 }
483 }
484 else
485 for (size_t relidx = 0; !result && relidx < nrels; ++relidx)
486 {
487 GElf_Rela rela_mem, *r = gelf_getrela (reldata, relidx,
488 &rela_mem);
489 if (r == NULL)
490 return DWFL_E_LIBELF;
491 result = relocate (r->r_offset, &r->r_addend,
492 GELF_R_TYPE (r->r_info),
493 GELF_R_SYM (r->r_info));
494 check_badreltype ();
495 if (partial)
496 switch (result)
497 {
498 case DWFL_E_NOERROR:
499 /* We applied the relocation. Elide it. */
500 memset (&rela_mem, 0, sizeof rela_mem);
501 gelf_update_rela (reldata, relidx, &rela_mem);
502 ++complete;
503 break;
504 case DWFL_E_BADRELTYPE:
505 case DWFL_E_RELUNDEF:
506 /* We couldn't handle this relocation. Skip it. */
507 result = DWFL_E_NOERROR;
508 break;
509 default:
510 break;
511 }
512 }
513
514 if (likely (result == DWFL_E_NOERROR))
515 {
516 if (!partial || complete == nrels)
517 /* Mark this relocation section as being empty now that we have
518 done its work. This affects unstrip -R, so e.g. it emits an
519 empty .rela.debug_info along with a .debug_info that has
520 already been fully relocated. */
521 nrels = 0;
522 else if (complete != 0)
523 {
524 /* We handled some of the relocations but not all.
525 We've zeroed out the ones we processed.
526 Now remove them from the section. */
527
528 size_t next = 0;
529 if (shdr->sh_type == SHT_REL)
530 for (size_t relidx = 0; relidx < nrels; ++relidx)
531 {
532 GElf_Rel rel_mem;
533 GElf_Rel *r = gelf_getrel (reldata, relidx, &rel_mem);
534 if (r->r_info != 0 || r->r_offset != 0)
535 {
536 if (next != relidx)
537 gelf_update_rel (reldata, next, r);
538 ++next;
539 }
540 }
541 else
542 for (size_t relidx = 0; relidx < nrels; ++relidx)
543 {
544 GElf_Rela rela_mem;
545 GElf_Rela *r = gelf_getrela (reldata, relidx, &rela_mem);
546 if (r->r_info != 0 || r->r_offset != 0 || r->r_addend != 0)
547 {
548 if (next != relidx)
549 gelf_update_rela (reldata, next, r);
550 ++next;
551 }
552 }
553 nrels = next;
554 }
555
556 shdr->sh_size = reldata->d_size = nrels * shdr->sh_entsize;
557 gelf_update_shdr (scn, shdr);
558 }
559
560 return result;
561 }
562
563 Dwfl_Error
564 internal_function
__libdwfl_relocate(Dwfl_Module * mod,Elf * debugfile,bool debug)565 __libdwfl_relocate (Dwfl_Module *mod, Elf *debugfile, bool debug)
566 {
567 assert (mod->e_type == ET_REL);
568
569 GElf_Ehdr ehdr_mem;
570 const GElf_Ehdr *ehdr = gelf_getehdr (debugfile, &ehdr_mem);
571 if (ehdr == NULL)
572 return DWFL_E_LIBELF;
573
574 size_t d_shstrndx;
575 if (elf_getshstrndx (debugfile, &d_shstrndx) < 0)
576 return DWFL_E_LIBELF;
577
578 RELOC_SYMTAB_CACHE (reloc_symtab);
579
580 /* Look at each section in the debuginfo file, and process the
581 relocation sections for debugging sections. */
582 Dwfl_Error result = DWFL_E_NOERROR;
583 Elf_Scn *scn = NULL;
584 while (result == DWFL_E_NOERROR
585 && (scn = elf_nextscn (debugfile, scn)) != NULL)
586 {
587 GElf_Shdr shdr_mem;
588 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
589
590 if ((shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)
591 && shdr->sh_size != 0)
592 {
593 /* It's a relocation section. */
594
595 Elf_Scn *tscn = elf_getscn (debugfile, shdr->sh_info);
596 if (unlikely (tscn == NULL))
597 result = DWFL_E_LIBELF;
598 else
599 result = relocate_section (mod, debugfile, ehdr, d_shstrndx,
600 &reloc_symtab, scn, shdr, tscn,
601 debug, !debug);
602 }
603 }
604
605 return result;
606 }
607
608 Dwfl_Error
609 internal_function
__libdwfl_relocate_section(Dwfl_Module * mod,Elf * relocated,Elf_Scn * relocscn,Elf_Scn * tscn,bool partial)610 __libdwfl_relocate_section (Dwfl_Module *mod, Elf *relocated,
611 Elf_Scn *relocscn, Elf_Scn *tscn, bool partial)
612 {
613 GElf_Ehdr ehdr_mem;
614 GElf_Shdr shdr_mem;
615
616 RELOC_SYMTAB_CACHE (reloc_symtab);
617
618 size_t shstrndx;
619 if (elf_getshstrndx (relocated, &shstrndx) < 0)
620 return DWFL_E_LIBELF;
621
622 return (__libdwfl_module_getebl (mod)
623 ?: relocate_section (mod, relocated,
624 gelf_getehdr (relocated, &ehdr_mem), shstrndx,
625 &reloc_symtab,
626 relocscn, gelf_getshdr (relocscn, &shdr_mem),
627 tscn, false, partial));
628 }
629