]> git.proxmox.com Git - rustc.git/blob - src/libstd/sys/unix/backtrace.rs
135ae1bf9163bc155bd312de295eed1150ac826f
[rustc.git] / src / libstd / sys / unix / backtrace.rs
1 // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /// Backtrace support built on libgcc with some extra OS-specific support
12 ///
13 /// Some methods of getting a backtrace:
14 ///
15 /// * The backtrace() functions on unix. It turns out this doesn't work very
16 /// well for green threads on OSX, and the address to symbol portion of it
17 /// suffers problems that are described below.
18 ///
19 /// * Using libunwind. This is more difficult than it sounds because libunwind
20 /// isn't installed everywhere by default. It's also a bit of a hefty library,
21 /// so possibly not the best option. When testing, libunwind was excellent at
22 /// getting both accurate backtraces and accurate symbols across platforms.
23 /// This route was not chosen in favor of the next option, however.
24 ///
25 /// * We're already using libgcc_s for exceptions in rust (triggering thread
26 /// unwinding and running destructors on the stack), and it turns out that it
27 /// conveniently comes with a function that also gives us a backtrace. All of
28 /// these functions look like _Unwind_*, but it's not quite the full
29 /// repertoire of the libunwind API. Due to it already being in use, this was
30 /// the chosen route of getting a backtrace.
31 ///
32 /// After choosing libgcc_s for backtraces, the sad part is that it will only
33 /// give us a stack trace of instruction pointers. Thankfully these instruction
34 /// pointers are accurate (they work for green and native threads), but it's
35 /// then up to us again to figure out how to translate these addresses to
36 /// symbols. As with before, we have a few options. Before, that, a little bit
37 /// of an interlude about symbols. This is my very limited knowledge about
38 /// symbol tables, and this information is likely slightly wrong, but the
39 /// general idea should be correct.
40 ///
41 /// When talking about symbols, it's helpful to know a few things about where
42 /// symbols are located. Some symbols are located in the dynamic symbol table
43 /// of the executable which in theory means that they're available for dynamic
44 /// linking and lookup. Other symbols end up only in the local symbol table of
45 /// the file. This loosely corresponds to pub and priv functions in Rust.
46 ///
47 /// Armed with this knowledge, we know that our solution for address to symbol
48 /// translation will need to consult both the local and dynamic symbol tables.
49 /// With that in mind, here's our options of translating an address to
50 /// a symbol.
51 ///
52 /// * Use dladdr(). The original backtrace()-based idea actually uses dladdr()
53 /// behind the scenes to translate, and this is why backtrace() was not used.
54 /// Conveniently, this method works fantastically on OSX. It appears dladdr()
55 /// uses magic to consult the local symbol table, or we're putting everything
56 /// in the dynamic symbol table anyway. Regardless, for OSX, this is the
57 /// method used for translation. It's provided by the system and easy to do.o
58 ///
59 /// Sadly, all other systems have a dladdr() implementation that does not
60 /// consult the local symbol table. This means that most functions are blank
61 /// because they don't have symbols. This means that we need another solution.
62 ///
63 /// * Use unw_get_proc_name(). This is part of the libunwind api (not the
64 /// libgcc_s version of the libunwind api), but involves taking a dependency
65 /// to libunwind. We may pursue this route in the future if we bundle
66 /// libunwind, but libunwind was unwieldy enough that it was not chosen at
67 /// this time to provide this functionality.
68 ///
69 /// * Shell out to a utility like `readelf`. Crazy though it may sound, it's a
70 /// semi-reasonable solution. The stdlib already knows how to spawn processes,
71 /// so in theory it could invoke readelf, parse the output, and consult the
72 /// local/dynamic symbol tables from there. This ended up not getting chosen
73 /// due to the craziness of the idea plus the advent of the next option.
74 ///
75 /// * Use `libbacktrace`. It turns out that this is a small library bundled in
76 /// the gcc repository which provides backtrace and symbol translation
77 /// functionality. All we really need from it is the backtrace functionality,
78 /// and we only really need this on everything that's not OSX, so this is the
79 /// chosen route for now.
80 ///
81 /// In summary, the current situation uses libgcc_s to get a trace of stack
82 /// pointers, and we use dladdr() or libbacktrace to translate these addresses
83 /// to symbols. This is a bit of a hokey implementation as-is, but it works for
84 /// all unix platforms we support right now, so it at least gets the job done.
85
86 use prelude::v1::*;
87 use io::prelude::*;
88
89 use ffi::CStr;
90 use io;
91 use libc;
92 use mem;
93 use str;
94 use sync::{StaticMutex, MUTEX_INIT};
95
96 use sys_common::backtrace::*;
97
98 /// As always - iOS on arm uses SjLj exceptions and
99 /// _Unwind_Backtrace is even not available there. Still,
100 /// backtraces could be extracted using a backtrace function,
101 /// which thanks god is public
102 ///
103 /// As mentioned in a huge comment block above, backtrace doesn't
104 /// play well with green threads, so while it is extremely nice
105 /// and simple to use it should be used only on iOS devices as the
106 /// only viable option.
107 #[cfg(all(target_os = "ios", target_arch = "arm"))]
108 #[inline(never)]
109 pub fn write(w: &mut Write) -> io::Result<()> {
110 use result;
111
112 extern {
113 fn backtrace(buf: *mut *mut libc::c_void,
114 sz: libc::c_int) -> libc::c_int;
115 }
116
117 // while it doesn't requires lock for work as everything is
118 // local, it still displays much nicer backtraces when a
119 // couple of threads panic simultaneously
120 static LOCK: StaticMutex = MUTEX_INIT;
121 let _g = LOCK.lock();
122
123 try!(writeln!(w, "stack backtrace:"));
124 // 100 lines should be enough
125 const SIZE: usize = 100;
126 let mut buf: [*mut libc::c_void; SIZE] = unsafe {mem::zeroed()};
127 let cnt = unsafe { backtrace(buf.as_mut_ptr(), SIZE as libc::c_int) as usize};
128
129 // skipping the first one as it is write itself
130 let iter = (1..cnt).map(|i| {
131 print(w, i as isize, buf[i], buf[i])
132 });
133 result::fold(iter, (), |_, _| ())
134 }
135
136 #[cfg(not(all(target_os = "ios", target_arch = "arm")))]
137 #[inline(never)] // if we know this is a function call, we can skip it when
138 // tracing
139 pub fn write(w: &mut Write) -> io::Result<()> {
140 struct Context<'a> {
141 idx: isize,
142 writer: &'a mut (Write+'a),
143 last_error: Option<io::Error>,
144 }
145
146 // When using libbacktrace, we use some necessary global state, so we
147 // need to prevent more than one thread from entering this block. This
148 // is semi-reasonable in terms of printing anyway, and we know that all
149 // I/O done here is blocking I/O, not green I/O, so we don't have to
150 // worry about this being a native vs green mutex.
151 static LOCK: StaticMutex = MUTEX_INIT;
152 let _g = LOCK.lock();
153
154 try!(writeln!(w, "stack backtrace:"));
155
156 let mut cx = Context { writer: w, last_error: None, idx: 0 };
157 return match unsafe {
158 uw::_Unwind_Backtrace(trace_fn,
159 &mut cx as *mut Context as *mut libc::c_void)
160 } {
161 uw::_URC_NO_REASON => {
162 match cx.last_error {
163 Some(err) => Err(err),
164 None => Ok(())
165 }
166 }
167 _ => Ok(()),
168 };
169
170 extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
171 arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
172 let cx: &mut Context = unsafe { mem::transmute(arg) };
173 let mut ip_before_insn = 0;
174 let mut ip = unsafe {
175 uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
176 };
177 if !ip.is_null() && ip_before_insn == 0 {
178 // this is a non-signaling frame, so `ip` refers to the address
179 // after the calling instruction. account for that.
180 ip = (ip as usize - 1) as *mut _;
181 }
182
183 // dladdr() on osx gets whiny when we use FindEnclosingFunction, and
184 // it appears to work fine without it, so we only use
185 // FindEnclosingFunction on non-osx platforms. In doing so, we get a
186 // slightly more accurate stack trace in the process.
187 //
188 // This is often because panic involves the last instruction of a
189 // function being "call std::rt::begin_unwind", with no ret
190 // instructions after it. This means that the return instruction
191 // pointer points *outside* of the calling function, and by
192 // unwinding it we go back to the original function.
193 let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
194 ip
195 } else {
196 unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
197 };
198
199 // Don't print out the first few frames (they're not user frames)
200 cx.idx += 1;
201 if cx.idx <= 0 { return uw::_URC_NO_REASON }
202 // Don't print ginormous backtraces
203 if cx.idx > 100 {
204 match write!(cx.writer, " ... <frames omitted>\n") {
205 Ok(()) => {}
206 Err(e) => { cx.last_error = Some(e); }
207 }
208 return uw::_URC_FAILURE
209 }
210
211 // Once we hit an error, stop trying to print more frames
212 if cx.last_error.is_some() { return uw::_URC_FAILURE }
213
214 match print(cx.writer, cx.idx, ip, symaddr) {
215 Ok(()) => {}
216 Err(e) => { cx.last_error = Some(e); }
217 }
218
219 // keep going
220 return uw::_URC_NO_REASON
221 }
222 }
223
224 #[cfg(any(target_os = "macos", target_os = "ios"))]
225 fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
226 _symaddr: *mut libc::c_void) -> io::Result<()> {
227 use intrinsics;
228 #[repr(C)]
229 struct Dl_info {
230 dli_fname: *const libc::c_char,
231 dli_fbase: *mut libc::c_void,
232 dli_sname: *const libc::c_char,
233 dli_saddr: *mut libc::c_void,
234 }
235 extern {
236 fn dladdr(addr: *const libc::c_void,
237 info: *mut Dl_info) -> libc::c_int;
238 }
239
240 let mut info: Dl_info = unsafe { intrinsics::init() };
241 if unsafe { dladdr(addr, &mut info) == 0 } {
242 output(w, idx,addr, None)
243 } else {
244 output(w, idx, addr, Some(unsafe {
245 CStr::from_ptr(info.dli_sname).to_bytes()
246 }))
247 }
248 }
249
250 #[cfg(not(any(target_os = "macos", target_os = "ios")))]
251 fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
252 symaddr: *mut libc::c_void) -> io::Result<()> {
253 use env;
254 use os::unix::prelude::*;
255 use ptr;
256
257 ////////////////////////////////////////////////////////////////////////
258 // libbacktrace.h API
259 ////////////////////////////////////////////////////////////////////////
260 type backtrace_syminfo_callback =
261 extern "C" fn(data: *mut libc::c_void,
262 pc: libc::uintptr_t,
263 symname: *const libc::c_char,
264 symval: libc::uintptr_t,
265 symsize: libc::uintptr_t);
266 type backtrace_full_callback =
267 extern "C" fn(data: *mut libc::c_void,
268 pc: libc::uintptr_t,
269 filename: *const libc::c_char,
270 lineno: libc::c_int,
271 function: *const libc::c_char) -> libc::c_int;
272 type backtrace_error_callback =
273 extern "C" fn(data: *mut libc::c_void,
274 msg: *const libc::c_char,
275 errnum: libc::c_int);
276 enum backtrace_state {}
277 #[link(name = "backtrace", kind = "static")]
278 #[cfg(not(test))]
279 extern {}
280
281 extern {
282 fn backtrace_create_state(filename: *const libc::c_char,
283 threaded: libc::c_int,
284 error: backtrace_error_callback,
285 data: *mut libc::c_void)
286 -> *mut backtrace_state;
287 fn backtrace_syminfo(state: *mut backtrace_state,
288 addr: libc::uintptr_t,
289 cb: backtrace_syminfo_callback,
290 error: backtrace_error_callback,
291 data: *mut libc::c_void) -> libc::c_int;
292 fn backtrace_pcinfo(state: *mut backtrace_state,
293 addr: libc::uintptr_t,
294 cb: backtrace_full_callback,
295 error: backtrace_error_callback,
296 data: *mut libc::c_void) -> libc::c_int;
297 }
298
299 ////////////////////////////////////////////////////////////////////////
300 // helper callbacks
301 ////////////////////////////////////////////////////////////////////////
302
303 type FileLine = (*const libc::c_char, libc::c_int);
304
305 extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
306 _errnum: libc::c_int) {
307 // do nothing for now
308 }
309 extern fn syminfo_cb(data: *mut libc::c_void,
310 _pc: libc::uintptr_t,
311 symname: *const libc::c_char,
312 _symval: libc::uintptr_t,
313 _symsize: libc::uintptr_t) {
314 let slot = data as *mut *const libc::c_char;
315 unsafe { *slot = symname; }
316 }
317 extern fn pcinfo_cb(data: *mut libc::c_void,
318 _pc: libc::uintptr_t,
319 filename: *const libc::c_char,
320 lineno: libc::c_int,
321 _function: *const libc::c_char) -> libc::c_int {
322 if !filename.is_null() {
323 let slot = data as *mut &mut [FileLine];
324 let buffer = unsafe {ptr::read(slot)};
325
326 // if the buffer is not full, add file:line to the buffer
327 // and adjust the buffer for next possible calls to pcinfo_cb.
328 if !buffer.is_empty() {
329 buffer[0] = (filename, lineno);
330 unsafe { ptr::write(slot, &mut buffer[1..]); }
331 }
332 }
333
334 0
335 }
336
337 // The libbacktrace API supports creating a state, but it does not
338 // support destroying a state. I personally take this to mean that a
339 // state is meant to be created and then live forever.
340 //
341 // I would love to register an at_exit() handler which cleans up this
342 // state, but libbacktrace provides no way to do so.
343 //
344 // With these constraints, this function has a statically cached state
345 // that is calculated the first time this is requested. Remember that
346 // backtracing all happens serially (one global lock).
347 //
348 // An additionally oddity in this function is that we initialize the
349 // filename via self_exe_name() to pass to libbacktrace. It turns out
350 // that on Linux libbacktrace seamlessly gets the filename of the
351 // current executable, but this fails on freebsd. by always providing
352 // it, we make sure that libbacktrace never has a reason to not look up
353 // the symbols. The libbacktrace API also states that the filename must
354 // be in "permanent memory", so we copy it to a static and then use the
355 // static as the pointer.
356 //
357 // FIXME: We also call self_exe_name() on DragonFly BSD. I haven't
358 // tested if this is required or not.
359 unsafe fn init_state() -> *mut backtrace_state {
360 static mut STATE: *mut backtrace_state = 0 as *mut backtrace_state;
361 static mut LAST_FILENAME: [libc::c_char; 256] = [0; 256];
362 if !STATE.is_null() { return STATE }
363 let selfname = if cfg!(target_os = "freebsd") ||
364 cfg!(target_os = "dragonfly") ||
365 cfg!(target_os = "bitrig") ||
366 cfg!(target_os = "openbsd") {
367 env::current_exe().ok()
368 } else {
369 None
370 };
371 let filename = match selfname {
372 Some(path) => {
373 let bytes = path.as_os_str().as_bytes();
374 if bytes.len() < LAST_FILENAME.len() {
375 let i = bytes.iter();
376 for (slot, val) in LAST_FILENAME.iter_mut().zip(i) {
377 *slot = *val as libc::c_char;
378 }
379 LAST_FILENAME.as_ptr()
380 } else {
381 ptr::null()
382 }
383 }
384 None => ptr::null(),
385 };
386 STATE = backtrace_create_state(filename, 0, error_cb,
387 ptr::null_mut());
388 return STATE
389 }
390
391 ////////////////////////////////////////////////////////////////////////
392 // translation
393 ////////////////////////////////////////////////////////////////////////
394
395 // backtrace errors are currently swept under the rug, only I/O
396 // errors are reported
397 let state = unsafe { init_state() };
398 if state.is_null() {
399 return output(w, idx, addr, None)
400 }
401 let mut data = ptr::null();
402 let data_addr = &mut data as *mut *const libc::c_char;
403 let ret = unsafe {
404 backtrace_syminfo(state, symaddr as libc::uintptr_t,
405 syminfo_cb, error_cb,
406 data_addr as *mut libc::c_void)
407 };
408 if ret == 0 || data.is_null() {
409 try!(output(w, idx, addr, None));
410 } else {
411 try!(output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() })));
412 }
413
414 // pcinfo may return an arbitrary number of file:line pairs,
415 // in the order of stack trace (i.e. inlined calls first).
416 // in order to avoid allocation, we stack-allocate a fixed size of entries.
417 const FILELINE_SIZE: usize = 32;
418 let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
419 let ret;
420 let fileline_count;
421 {
422 let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
423 let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
424 ret = unsafe {
425 backtrace_pcinfo(state, addr as libc::uintptr_t,
426 pcinfo_cb, error_cb,
427 fileline_addr as *mut libc::c_void)
428 };
429 fileline_count = FILELINE_SIZE - fileline_win.len();
430 }
431 if ret == 0 {
432 for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() {
433 if file.is_null() { continue; } // just to be sure
434 let file = unsafe { CStr::from_ptr(file).to_bytes() };
435 try!(output_fileline(w, file, line, i == FILELINE_SIZE - 1));
436 }
437 }
438
439 Ok(())
440 }
441
442 // Finally, after all that work above, we can emit a symbol.
443 fn output(w: &mut Write, idx: isize, addr: *mut libc::c_void,
444 s: Option<&[u8]>) -> io::Result<()> {
445 try!(write!(w, " {:2}: {:2$?} - ", idx, addr, HEX_WIDTH));
446 match s.and_then(|s| str::from_utf8(s).ok()) {
447 Some(string) => try!(demangle(w, string)),
448 None => try!(write!(w, "<unknown>")),
449 }
450 w.write_all(&['\n' as u8])
451 }
452
453 #[allow(dead_code)]
454 fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
455 more: bool) -> io::Result<()> {
456 let file = str::from_utf8(file).unwrap_or("<unknown>");
457 // prior line: " ##: {:2$} - func"
458 try!(write!(w, " {:3$}at {}:{}", "", file, line, HEX_WIDTH));
459 if more {
460 try!(write!(w, " <... and possibly more>"));
461 }
462 w.write_all(&['\n' as u8])
463 }
464
465 /// Unwind library interface used for backtraces
466 ///
467 /// Note that dead code is allowed as here are just bindings
468 /// iOS doesn't use all of them it but adding more
469 /// platform-specific configs pollutes the code too much
470 #[allow(non_camel_case_types)]
471 #[allow(non_snake_case)]
472 #[allow(dead_code)]
473 mod uw {
474 pub use self::_Unwind_Reason_Code::*;
475
476 use libc;
477
478 #[repr(C)]
479 pub enum _Unwind_Reason_Code {
480 _URC_NO_REASON = 0,
481 _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
482 _URC_FATAL_PHASE2_ERROR = 2,
483 _URC_FATAL_PHASE1_ERROR = 3,
484 _URC_NORMAL_STOP = 4,
485 _URC_END_OF_STACK = 5,
486 _URC_HANDLER_FOUND = 6,
487 _URC_INSTALL_CONTEXT = 7,
488 _URC_CONTINUE_UNWIND = 8,
489 _URC_FAILURE = 9, // used only by ARM EABI
490 }
491
492 pub enum _Unwind_Context {}
493
494 pub type _Unwind_Trace_Fn =
495 extern fn(ctx: *mut _Unwind_Context,
496 arg: *mut libc::c_void) -> _Unwind_Reason_Code;
497
498 extern {
499 // No native _Unwind_Backtrace on iOS
500 #[cfg(not(all(target_os = "ios", target_arch = "arm")))]
501 pub fn _Unwind_Backtrace(trace: _Unwind_Trace_Fn,
502 trace_argument: *mut libc::c_void)
503 -> _Unwind_Reason_Code;
504
505 // available since GCC 4.2.0, should be fine for our purpose
506 #[cfg(all(not(all(target_os = "android", target_arch = "arm")),
507 not(all(target_os = "linux", target_arch = "arm"))))]
508 pub fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context,
509 ip_before_insn: *mut libc::c_int)
510 -> libc::uintptr_t;
511
512 #[cfg(all(not(target_os = "android"),
513 not(all(target_os = "linux", target_arch = "arm"))))]
514 pub fn _Unwind_FindEnclosingFunction(pc: *mut libc::c_void)
515 -> *mut libc::c_void;
516 }
517
518 // On android, the function _Unwind_GetIP is a macro, and this is the
519 // expansion of the macro. This is all copy/pasted directly from the
520 // header file with the definition of _Unwind_GetIP.
521 #[cfg(any(all(target_os = "android", target_arch = "arm"),
522 all(target_os = "linux", target_arch = "arm")))]
523 pub unsafe fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t {
524 #[repr(C)]
525 enum _Unwind_VRS_Result {
526 _UVRSR_OK = 0,
527 _UVRSR_NOT_IMPLEMENTED = 1,
528 _UVRSR_FAILED = 2,
529 }
530 #[repr(C)]
531 enum _Unwind_VRS_RegClass {
532 _UVRSC_CORE = 0,
533 _UVRSC_VFP = 1,
534 _UVRSC_FPA = 2,
535 _UVRSC_WMMXD = 3,
536 _UVRSC_WMMXC = 4,
537 }
538 #[repr(C)]
539 enum _Unwind_VRS_DataRepresentation {
540 _UVRSD_UINT32 = 0,
541 _UVRSD_VFPX = 1,
542 _UVRSD_FPAX = 2,
543 _UVRSD_UINT64 = 3,
544 _UVRSD_FLOAT = 4,
545 _UVRSD_DOUBLE = 5,
546 }
547
548 type _Unwind_Word = libc::c_uint;
549 extern {
550 fn _Unwind_VRS_Get(ctx: *mut _Unwind_Context,
551 klass: _Unwind_VRS_RegClass,
552 word: _Unwind_Word,
553 repr: _Unwind_VRS_DataRepresentation,
554 data: *mut libc::c_void)
555 -> _Unwind_VRS_Result;
556 }
557
558 let mut val: _Unwind_Word = 0;
559 let ptr = &mut val as *mut _Unwind_Word;
560 let _ = _Unwind_VRS_Get(ctx, _Unwind_VRS_RegClass::_UVRSC_CORE, 15,
561 _Unwind_VRS_DataRepresentation::_UVRSD_UINT32,
562 ptr as *mut libc::c_void);
563 (val & !1) as libc::uintptr_t
564 }
565
566 // This function doesn't exist on Android or ARM/Linux, so make it same
567 // to _Unwind_GetIP
568 #[cfg(any(all(target_os = "android", target_arch = "arm"),
569 all(target_os = "linux", target_arch = "arm")))]
570 pub unsafe fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context,
571 ip_before_insn: *mut libc::c_int)
572 -> libc::uintptr_t
573 {
574 *ip_before_insn = 0;
575 _Unwind_GetIP(ctx)
576 }
577
578 // This function also doesn't exist on Android or ARM/Linux, so make it
579 // a no-op
580 #[cfg(any(target_os = "android",
581 all(target_os = "linux", target_arch = "arm")))]
582 pub unsafe fn _Unwind_FindEnclosingFunction(pc: *mut libc::c_void)
583 -> *mut libc::c_void
584 {
585 pc
586 }
587 }