rtos: Destroy RTOS and fix memory leak
[openocd.git] / src / rtos / rtos.c
1 /***************************************************************************
2 * Copyright (C) 2011 by Broadcom Corporation *
3 * Evan Hunter - ehunter@broadcom.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 ***************************************************************************/
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "rtos.h"
24 #include "target/target.h"
25 #include "helper/log.h"
26 #include "helper/binarybuffer.h"
27 #include "server/gdb_server.h"
28
29 /* RTOSs */
30 extern struct rtos_type FreeRTOS_rtos;
31 extern struct rtos_type ThreadX_rtos;
32 extern struct rtos_type eCos_rtos;
33 extern struct rtos_type Linux_os;
34 extern struct rtos_type ChibiOS_rtos;
35 extern struct rtos_type chromium_ec_rtos;
36 extern struct rtos_type embKernel_rtos;
37 extern struct rtos_type mqx_rtos;
38 extern struct rtos_type uCOS_III_rtos;
39 extern struct rtos_type nuttx_rtos;
40 extern struct rtos_type hwthread_rtos;
41
42 static struct rtos_type *rtos_types[] = {
43 &ThreadX_rtos,
44 &FreeRTOS_rtos,
45 &eCos_rtos,
46 &Linux_os,
47 &ChibiOS_rtos,
48 &chromium_ec_rtos,
49 &embKernel_rtos,
50 &mqx_rtos,
51 &uCOS_III_rtos,
52 &nuttx_rtos,
53 &hwthread_rtos,
54 NULL
55 };
56
57 int rtos_thread_packet(struct connection *connection, const char *packet, int packet_size);
58
59 int rtos_smp_init(struct target *target)
60 {
61 if (target->rtos->type->smp_init)
62 return target->rtos->type->smp_init(target);
63 return ERROR_TARGET_INIT_FAILED;
64 }
65
66 static int rtos_target_for_threadid(struct connection *connection, int64_t threadid, struct target **t)
67 {
68 struct target *curr = get_target_from_connection(connection);
69 if (t)
70 *t = curr;
71
72 return ERROR_OK;
73 }
74
75 static int os_alloc(struct target *target, struct rtos_type *ostype)
76 {
77 struct rtos *os = target->rtos = calloc(1, sizeof(struct rtos));
78
79 if (!os)
80 return JIM_ERR;
81
82 os->type = ostype;
83 os->current_threadid = -1;
84 os->current_thread = 0;
85 os->symbols = NULL;
86 os->target = target;
87
88 /* RTOS drivers can override the packet handler in _create(). */
89 os->gdb_thread_packet = rtos_thread_packet;
90 os->gdb_target_for_threadid = rtos_target_for_threadid;
91
92 return JIM_OK;
93 }
94
95 static void os_free(struct target *target)
96 {
97 if (!target->rtos)
98 return;
99
100 if (target->rtos->symbols)
101 free(target->rtos->symbols);
102
103 free(target->rtos);
104 target->rtos = NULL;
105 }
106
107 static int os_alloc_create(struct target *target, struct rtos_type *ostype)
108 {
109 int ret = os_alloc(target, ostype);
110
111 if (JIM_OK == ret) {
112 ret = target->rtos->type->create(target);
113 if (ret != JIM_OK)
114 os_free(target);
115 }
116
117 return ret;
118 }
119
120 int rtos_create(Jim_GetOptInfo *goi, struct target *target)
121 {
122 int x;
123 const char *cp;
124 struct Jim_Obj *res;
125 int e;
126
127 if (!goi->isconfigure && goi->argc != 0) {
128 Jim_WrongNumArgs(goi->interp, goi->argc, goi->argv, "NO PARAMS");
129 return JIM_ERR;
130 }
131
132 os_free(target);
133
134 e = Jim_GetOpt_String(goi, &cp, NULL);
135 if (e != JIM_OK)
136 return e;
137
138 if (0 == strcmp(cp, "auto")) {
139 /* Auto detect tries to look up all symbols for each RTOS,
140 * and runs the RTOS driver's _detect() function when GDB
141 * finds all symbols for any RTOS. See rtos_qsymbol(). */
142 target->rtos_auto_detect = true;
143
144 /* rtos_qsymbol() will iterate over all RTOSes. Allocate
145 * target->rtos here, and set it to the first RTOS type. */
146 return os_alloc(target, rtos_types[0]);
147 }
148
149 for (x = 0; rtos_types[x]; x++)
150 if (0 == strcmp(cp, rtos_types[x]->name))
151 return os_alloc_create(target, rtos_types[x]);
152
153 Jim_SetResultFormatted(goi->interp, "Unknown RTOS type %s, try one of: ", cp);
154 res = Jim_GetResult(goi->interp);
155 for (x = 0; rtos_types[x]; x++)
156 Jim_AppendStrings(goi->interp, res, rtos_types[x]->name, ", ", NULL);
157 Jim_AppendStrings(goi->interp, res, " or auto", NULL);
158
159 return JIM_ERR;
160 }
161
162 void rtos_destroy(struct target *target)
163 {
164 os_free(target);
165 }
166
167 int gdb_thread_packet(struct connection *connection, char const *packet, int packet_size)
168 {
169 struct target *target = get_target_from_connection(connection);
170 if (target->rtos == NULL)
171 return rtos_thread_packet(connection, packet, packet_size); /* thread not
172 *found*/
173 return target->rtos->gdb_thread_packet(connection, packet, packet_size);
174 }
175
176 static symbol_table_elem_t *next_symbol(struct rtos *os, char *cur_symbol, uint64_t cur_addr)
177 {
178 symbol_table_elem_t *s;
179
180 if (!os->symbols)
181 os->type->get_symbol_list_to_lookup(&os->symbols);
182
183 if (!cur_symbol[0])
184 return &os->symbols[0];
185
186 for (s = os->symbols; s->symbol_name; s++)
187 if (!strcmp(s->symbol_name, cur_symbol)) {
188 s->address = cur_addr;
189 s++;
190 return s;
191 }
192
193 return NULL;
194 }
195
196 /* searches for 'symbol' in the lookup table for 'os' and returns TRUE,
197 * if 'symbol' is not declared optional */
198 static bool is_symbol_mandatory(const struct rtos *os, const char *symbol)
199 {
200 for (symbol_table_elem_t *s = os->symbols; s->symbol_name; ++s) {
201 if (!strcmp(s->symbol_name, symbol))
202 return !s->optional;
203 }
204 return false;
205 }
206
207 /* rtos_qsymbol() processes and replies to all qSymbol packets from GDB.
208 *
209 * GDB sends a qSymbol:: packet (empty address, empty name) to notify
210 * that it can now answer qSymbol::hexcodedname queries, to look up symbols.
211 *
212 * If the qSymbol packet has no address that means GDB did not find the
213 * symbol, in which case auto-detect will move on to try the next RTOS.
214 *
215 * rtos_qsymbol() then calls the next_symbol() helper function, which
216 * iterates over symbol names for the current RTOS until it finds the
217 * symbol in the received GDB packet, and then returns the next entry
218 * in the list of symbols.
219 *
220 * If GDB replied about the last symbol for the RTOS and the RTOS was
221 * specified explicitly, then no further symbol lookup is done. When
222 * auto-detecting, the RTOS driver _detect() function must return success.
223 *
224 * rtos_qsymbol() returns 1 if an RTOS has been detected, or 0 otherwise.
225 */
226 int rtos_qsymbol(struct connection *connection, char const *packet, int packet_size)
227 {
228 int rtos_detected = 0;
229 uint64_t addr = 0;
230 size_t reply_len;
231 char reply[GDB_BUFFER_SIZE + 1], cur_sym[GDB_BUFFER_SIZE / 2 + 1] = ""; /* Extra byte for nul-termination */
232 symbol_table_elem_t *next_sym = NULL;
233 struct target *target = get_target_from_connection(connection);
234 struct rtos *os = target->rtos;
235
236 reply_len = sprintf(reply, "OK");
237
238 if (!os)
239 goto done;
240
241 /* Decode any symbol name in the packet*/
242 size_t len = unhexify((uint8_t *)cur_sym, strchr(packet + 8, ':') + 1, strlen(strchr(packet + 8, ':') + 1));
243 cur_sym[len] = 0;
244
245 if ((strcmp(packet, "qSymbol::") != 0) && /* GDB is not offering symbol lookup for the first time */
246 (!sscanf(packet, "qSymbol:%" SCNx64 ":", &addr)) && /* GDB did not find an address for a symbol */
247 is_symbol_mandatory(os, cur_sym)) { /* the symbol is mandatory for this RTOS */
248
249 /* GDB could not find an address for the previous symbol */
250 if (!target->rtos_auto_detect) {
251 LOG_WARNING("RTOS %s not detected. (GDB could not find symbol \'%s\')", os->type->name, cur_sym);
252 goto done;
253 } else {
254 /* Autodetecting RTOS - try next RTOS */
255 if (!rtos_try_next(target)) {
256 LOG_WARNING("No RTOS could be auto-detected!");
257 goto done;
258 }
259
260 /* Next RTOS selected - invalidate current symbol */
261 cur_sym[0] = '\x00';
262 }
263 }
264 next_sym = next_symbol(os, cur_sym, addr);
265
266 if (!next_sym->symbol_name) {
267 /* No more symbols need looking up */
268
269 if (!target->rtos_auto_detect) {
270 rtos_detected = 1;
271 goto done;
272 }
273
274 if (os->type->detect_rtos(target)) {
275 LOG_INFO("Auto-detected RTOS: %s", os->type->name);
276 rtos_detected = 1;
277 goto done;
278 } else {
279 LOG_WARNING("No RTOS could be auto-detected!");
280 goto done;
281 }
282 }
283
284 if (8 + (strlen(next_sym->symbol_name) * 2) + 1 > sizeof(reply)) {
285 LOG_ERROR("ERROR: RTOS symbol '%s' name is too long for GDB!", next_sym->symbol_name);
286 goto done;
287 }
288
289 reply_len = snprintf(reply, sizeof(reply), "qSymbol:");
290 reply_len += hexify(reply + reply_len,
291 (const uint8_t *)next_sym->symbol_name, strlen(next_sym->symbol_name),
292 sizeof(reply) - reply_len);
293
294 done:
295 gdb_put_packet(connection, reply, reply_len);
296 return rtos_detected;
297 }
298
299 int rtos_thread_packet(struct connection *connection, char const *packet, int packet_size)
300 {
301 struct target *target = get_target_from_connection(connection);
302
303 if (strncmp(packet, "qThreadExtraInfo,", 17) == 0) {
304 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL) &&
305 (target->rtos->thread_count != 0)) {
306 threadid_t threadid = 0;
307 int found = -1;
308 sscanf(packet, "qThreadExtraInfo,%" SCNx64, &threadid);
309
310 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
311 int thread_num;
312 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
313 if (target->rtos->thread_details[thread_num].threadid == threadid) {
314 if (target->rtos->thread_details[thread_num].exists)
315 found = thread_num;
316 }
317 }
318 }
319 if (found == -1) {
320 gdb_put_packet(connection, "E01", 3); /* thread not found */
321 return ERROR_OK;
322 }
323
324 struct thread_detail *detail = &target->rtos->thread_details[found];
325
326 int str_size = 0;
327 if (detail->thread_name_str != NULL)
328 str_size += strlen(detail->thread_name_str);
329 if (detail->extra_info_str != NULL)
330 str_size += strlen(detail->extra_info_str);
331
332 char *tmp_str = calloc(str_size + 9, sizeof(char));
333 char *tmp_str_ptr = tmp_str;
334
335 if (detail->thread_name_str != NULL)
336 tmp_str_ptr += sprintf(tmp_str_ptr, "Name: %s", detail->thread_name_str);
337 if (detail->extra_info_str != NULL) {
338 if (tmp_str_ptr != tmp_str)
339 tmp_str_ptr += sprintf(tmp_str_ptr, ", ");
340 tmp_str_ptr += sprintf(tmp_str_ptr, "%s", detail->extra_info_str);
341 }
342
343 assert(strlen(tmp_str) ==
344 (size_t) (tmp_str_ptr - tmp_str));
345
346 char *hex_str = malloc(strlen(tmp_str) * 2 + 1);
347 size_t pkt_len = hexify(hex_str, (const uint8_t *)tmp_str,
348 strlen(tmp_str), strlen(tmp_str) * 2 + 1);
349
350 gdb_put_packet(connection, hex_str, pkt_len);
351 free(hex_str);
352 free(tmp_str);
353 return ERROR_OK;
354
355 }
356 gdb_put_packet(connection, "", 0);
357 return ERROR_OK;
358 } else if (strncmp(packet, "qSymbol", 7) == 0) {
359 if (rtos_qsymbol(connection, packet, packet_size) == 1) {
360 if (target->rtos_auto_detect == true) {
361 target->rtos_auto_detect = false;
362 target->rtos->type->create(target);
363 }
364 target->rtos->type->update_threads(target->rtos);
365 }
366 return ERROR_OK;
367 } else if (strncmp(packet, "qfThreadInfo", 12) == 0) {
368 int i;
369 if (target->rtos != NULL) {
370 if (target->rtos->thread_count == 0) {
371 gdb_put_packet(connection, "l", 1);
372 } else {
373 /*thread id are 16 char +1 for ',' */
374 char *out_str = malloc(17 * target->rtos->thread_count + 1);
375 char *tmp_str = out_str;
376 for (i = 0; i < target->rtos->thread_count; i++) {
377 tmp_str += sprintf(tmp_str, "%c%016" PRIx64, i == 0 ? 'm' : ',',
378 target->rtos->thread_details[i].threadid);
379 }
380 gdb_put_packet(connection, out_str, strlen(out_str));
381 free(out_str);
382 }
383 } else
384 gdb_put_packet(connection, "l", 1);
385
386 return ERROR_OK;
387 } else if (strncmp(packet, "qsThreadInfo", 12) == 0) {
388 gdb_put_packet(connection, "l", 1);
389 return ERROR_OK;
390 } else if (strncmp(packet, "qAttached", 9) == 0) {
391 gdb_put_packet(connection, "1", 1);
392 return ERROR_OK;
393 } else if (strncmp(packet, "qOffsets", 8) == 0) {
394 char offsets[] = "Text=0;Data=0;Bss=0";
395 gdb_put_packet(connection, offsets, sizeof(offsets)-1);
396 return ERROR_OK;
397 } else if (strncmp(packet, "qCRC:", 5) == 0) {
398 /* make sure we check this before "qC" packet below
399 * otherwise it gets incorrectly handled */
400 return GDB_THREAD_PACKET_NOT_CONSUMED;
401 } else if (strncmp(packet, "qC", 2) == 0) {
402 if (target->rtos != NULL) {
403 char buffer[19];
404 int size;
405 size = snprintf(buffer, 19, "QC%016" PRIx64, target->rtos->current_thread);
406 gdb_put_packet(connection, buffer, size);
407 } else
408 gdb_put_packet(connection, "QC0", 3);
409 return ERROR_OK;
410 } else if (packet[0] == 'T') { /* Is thread alive? */
411 threadid_t threadid;
412 int found = -1;
413 sscanf(packet, "T%" SCNx64, &threadid);
414 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
415 int thread_num;
416 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
417 if (target->rtos->thread_details[thread_num].threadid == threadid) {
418 if (target->rtos->thread_details[thread_num].exists)
419 found = thread_num;
420 }
421 }
422 }
423 if (found != -1)
424 gdb_put_packet(connection, "OK", 2); /* thread alive */
425 else
426 gdb_put_packet(connection, "E01", 3); /* thread not found */
427 return ERROR_OK;
428 } else if (packet[0] == 'H') { /* Set current thread ( 'c' for step and continue, 'g' for
429 * all other operations ) */
430 if ((packet[1] == 'g') && (target->rtos != NULL)) {
431 threadid_t threadid;
432 sscanf(packet, "Hg%16" SCNx64, &threadid);
433 LOG_DEBUG("RTOS: GDB requested to set current thread to 0x%" PRIx64, threadid);
434 /* threadid of 0 indicates target should choose */
435 if (threadid == 0)
436 target->rtos->current_threadid = target->rtos->current_thread;
437 else
438 target->rtos->current_threadid = threadid;
439 }
440 gdb_put_packet(connection, "OK", 2);
441 return ERROR_OK;
442 }
443
444 return GDB_THREAD_PACKET_NOT_CONSUMED;
445 }
446
447 static int rtos_put_gdb_reg_list(struct connection *connection,
448 struct rtos_reg *reg_list, int num_regs)
449 {
450 size_t num_bytes = 1; /* NUL */
451 for (int i = 0; i < num_regs; ++i)
452 num_bytes += DIV_ROUND_UP(reg_list[i].size, 8) * 2;
453
454 char *hex = malloc(num_bytes);
455 char *hex_p = hex;
456
457 for (int i = 0; i < num_regs; ++i) {
458 size_t count = DIV_ROUND_UP(reg_list[i].size, 8);
459 size_t n = hexify(hex_p, reg_list[i].value, count, num_bytes);
460 hex_p += n;
461 num_bytes -= n;
462 }
463
464 gdb_put_packet(connection, hex, strlen(hex));
465 free(hex);
466
467 return ERROR_OK;
468 }
469
470 /** Look through all registers to find this register. */
471 int rtos_get_gdb_reg(struct connection *connection, int reg_num)
472 {
473 struct target *target = get_target_from_connection(connection);
474 int64_t current_threadid = target->rtos->current_threadid;
475 if ((target->rtos != NULL) && (current_threadid != -1) &&
476 (current_threadid != 0) &&
477 ((current_threadid != target->rtos->current_thread) ||
478 (target->smp))) { /* in smp several current thread are possible */
479 struct rtos_reg *reg_list;
480 int num_regs;
481
482 LOG_DEBUG("getting register %d for thread 0x%" PRIx64
483 ", target->rtos->current_thread=0x%" PRIx64,
484 reg_num,
485 current_threadid,
486 target->rtos->current_thread);
487
488 int retval;
489 if (target->rtos->type->get_thread_reg) {
490 reg_list = calloc(1, sizeof(*reg_list));
491 num_regs = 1;
492 retval = target->rtos->type->get_thread_reg(target->rtos,
493 current_threadid, reg_num, &reg_list[0]);
494 if (retval != ERROR_OK) {
495 LOG_ERROR("RTOS: failed to get register %d", reg_num);
496 return retval;
497 }
498 } else {
499 retval = target->rtos->type->get_thread_reg_list(target->rtos,
500 current_threadid,
501 &reg_list,
502 &num_regs);
503 if (retval != ERROR_OK) {
504 LOG_ERROR("RTOS: failed to get register list");
505 return retval;
506 }
507 }
508
509 for (int i = 0; i < num_regs; ++i) {
510 if (reg_list[i].number == (uint32_t)reg_num) {
511 rtos_put_gdb_reg_list(connection, reg_list + i, 1);
512 free(reg_list);
513 return ERROR_OK;
514 }
515 }
516
517 free(reg_list);
518 }
519 return ERROR_FAIL;
520 }
521
522 /** Return a list of general registers. */
523 int rtos_get_gdb_reg_list(struct connection *connection)
524 {
525 struct target *target = get_target_from_connection(connection);
526 int64_t current_threadid = target->rtos->current_threadid;
527 if ((target->rtos != NULL) && (current_threadid != -1) &&
528 (current_threadid != 0) &&
529 ((current_threadid != target->rtos->current_thread) ||
530 (target->smp))) { /* in smp several current thread are possible */
531 struct rtos_reg *reg_list;
532 int num_regs;
533
534 LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
535 ", target->rtos->current_thread=0x%" PRIx64 "\r\n",
536 current_threadid,
537 target->rtos->current_thread);
538
539 int retval = target->rtos->type->get_thread_reg_list(target->rtos,
540 current_threadid,
541 &reg_list,
542 &num_regs);
543 if (retval != ERROR_OK) {
544 LOG_ERROR("RTOS: failed to get register list");
545 return retval;
546 }
547
548 rtos_put_gdb_reg_list(connection, reg_list, num_regs);
549 free(reg_list);
550
551 return ERROR_OK;
552 }
553 return ERROR_FAIL;
554 }
555
556 int rtos_set_reg(struct connection *connection, int reg_num,
557 uint8_t *reg_value)
558 {
559 struct target *target = get_target_from_connection(connection);
560 int64_t current_threadid = target->rtos->current_threadid;
561 if ((target->rtos != NULL) &&
562 (target->rtos->type->set_reg != NULL) &&
563 (current_threadid != -1) &&
564 (current_threadid != 0)) {
565 return target->rtos->type->set_reg(target->rtos, reg_num, reg_value);
566 }
567 return ERROR_FAIL;
568 }
569
570 int rtos_generic_stack_read(struct target *target,
571 const struct rtos_register_stacking *stacking,
572 int64_t stack_ptr,
573 struct rtos_reg **reg_list,
574 int *num_regs)
575 {
576 int retval;
577
578 if (stack_ptr == 0) {
579 LOG_ERROR("Error: null stack pointer in thread");
580 return -5;
581 }
582 /* Read the stack */
583 uint8_t *stack_data = malloc(stacking->stack_registers_size);
584 uint32_t address = stack_ptr;
585
586 if (stacking->stack_growth_direction == 1)
587 address -= stacking->stack_registers_size;
588 retval = target_read_buffer(target, address, stacking->stack_registers_size, stack_data);
589 if (retval != ERROR_OK) {
590 free(stack_data);
591 LOG_ERROR("Error reading stack frame from thread");
592 return retval;
593 }
594 LOG_DEBUG("RTOS: Read stack frame at 0x%" PRIx32, address);
595
596 #if 0
597 LOG_OUTPUT("Stack Data :");
598 for (i = 0; i < stacking->stack_registers_size; i++)
599 LOG_OUTPUT("%02X", stack_data[i]);
600 LOG_OUTPUT("\r\n");
601 #endif
602
603 int64_t new_stack_ptr;
604 if (stacking->calculate_process_stack != NULL) {
605 new_stack_ptr = stacking->calculate_process_stack(target,
606 stack_data, stacking, stack_ptr);
607 } else {
608 new_stack_ptr = stack_ptr - stacking->stack_growth_direction *
609 stacking->stack_registers_size;
610 }
611
612 *reg_list = calloc(stacking->num_output_registers, sizeof(struct rtos_reg));
613 *num_regs = stacking->num_output_registers;
614
615 for (int i = 0; i < stacking->num_output_registers; ++i) {
616 (*reg_list)[i].number = stacking->register_offsets[i].number;
617 (*reg_list)[i].size = stacking->register_offsets[i].width_bits;
618
619 int offset = stacking->register_offsets[i].offset;
620 if (offset == -2)
621 buf_cpy(&new_stack_ptr, (*reg_list)[i].value, (*reg_list)[i].size);
622 else if (offset != -1)
623 buf_cpy(stack_data + offset, (*reg_list)[i].value, (*reg_list)[i].size);
624 }
625
626 free(stack_data);
627 /* LOG_OUTPUT("Output register string: %s\r\n", *hex_reg_list); */
628 return ERROR_OK;
629 }
630
631 int rtos_try_next(struct target *target)
632 {
633 struct rtos *os = target->rtos;
634 struct rtos_type **type = rtos_types;
635
636 if (!os)
637 return 0;
638
639 while (*type && os->type != *type)
640 type++;
641
642 if (!*type || !*(++type))
643 return 0;
644
645 os->type = *type;
646 if (os->symbols) {
647 free(os->symbols);
648 os->symbols = NULL;
649 }
650
651 return 1;
652 }
653
654 int rtos_update_threads(struct target *target)
655 {
656 if ((target->rtos != NULL) && (target->rtos->type != NULL))
657 target->rtos->type->update_threads(target->rtos);
658 return ERROR_OK;
659 }
660
661 void rtos_free_threadlist(struct rtos *rtos)
662 {
663 if (rtos->thread_details) {
664 int j;
665
666 for (j = 0; j < rtos->thread_count; j++) {
667 struct thread_detail *current_thread = &rtos->thread_details[j];
668 free(current_thread->thread_name_str);
669 free(current_thread->extra_info_str);
670 }
671 free(rtos->thread_details);
672 rtos->thread_details = NULL;
673 rtos->thread_count = 0;
674 rtos->current_threadid = -1;
675 rtos->current_thread = 0;
676 }
677 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)