002d7b543c1448e0db2b7c638a736d7131941262
[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 int gdb_thread_packet(struct connection *connection, char const *packet, int packet_size)
163 {
164 struct target *target = get_target_from_connection(connection);
165 if (target->rtos == NULL)
166 return rtos_thread_packet(connection, packet, packet_size); /* thread not
167 *found*/
168 return target->rtos->gdb_thread_packet(connection, packet, packet_size);
169 }
170
171 static symbol_table_elem_t *next_symbol(struct rtos *os, char *cur_symbol, uint64_t cur_addr)
172 {
173 symbol_table_elem_t *s;
174
175 if (!os->symbols)
176 os->type->get_symbol_list_to_lookup(&os->symbols);
177
178 if (!cur_symbol[0])
179 return &os->symbols[0];
180
181 for (s = os->symbols; s->symbol_name; s++)
182 if (!strcmp(s->symbol_name, cur_symbol)) {
183 s->address = cur_addr;
184 s++;
185 return s;
186 }
187
188 return NULL;
189 }
190
191 /* searches for 'symbol' in the lookup table for 'os' and returns TRUE,
192 * if 'symbol' is not declared optional */
193 static bool is_symbol_mandatory(const struct rtos *os, const char *symbol)
194 {
195 for (symbol_table_elem_t *s = os->symbols; s->symbol_name; ++s) {
196 if (!strcmp(s->symbol_name, symbol))
197 return !s->optional;
198 }
199 return false;
200 }
201
202 /* rtos_qsymbol() processes and replies to all qSymbol packets from GDB.
203 *
204 * GDB sends a qSymbol:: packet (empty address, empty name) to notify
205 * that it can now answer qSymbol::hexcodedname queries, to look up symbols.
206 *
207 * If the qSymbol packet has no address that means GDB did not find the
208 * symbol, in which case auto-detect will move on to try the next RTOS.
209 *
210 * rtos_qsymbol() then calls the next_symbol() helper function, which
211 * iterates over symbol names for the current RTOS until it finds the
212 * symbol in the received GDB packet, and then returns the next entry
213 * in the list of symbols.
214 *
215 * If GDB replied about the last symbol for the RTOS and the RTOS was
216 * specified explicitly, then no further symbol lookup is done. When
217 * auto-detecting, the RTOS driver _detect() function must return success.
218 *
219 * rtos_qsymbol() returns 1 if an RTOS has been detected, or 0 otherwise.
220 */
221 int rtos_qsymbol(struct connection *connection, char const *packet, int packet_size)
222 {
223 int rtos_detected = 0;
224 uint64_t addr = 0;
225 size_t reply_len;
226 char reply[GDB_BUFFER_SIZE + 1], cur_sym[GDB_BUFFER_SIZE / 2 + 1] = ""; /* Extra byte for nul-termination */
227 symbol_table_elem_t *next_sym = NULL;
228 struct target *target = get_target_from_connection(connection);
229 struct rtos *os = target->rtos;
230
231 reply_len = sprintf(reply, "OK");
232
233 if (!os)
234 goto done;
235
236 /* Decode any symbol name in the packet*/
237 size_t len = unhexify((uint8_t *)cur_sym, strchr(packet + 8, ':') + 1, strlen(strchr(packet + 8, ':') + 1));
238 cur_sym[len] = 0;
239
240 if ((strcmp(packet, "qSymbol::") != 0) && /* GDB is not offering symbol lookup for the first time */
241 (!sscanf(packet, "qSymbol:%" SCNx64 ":", &addr)) && /* GDB did not find an address for a symbol */
242 is_symbol_mandatory(os, cur_sym)) { /* the symbol is mandatory for this RTOS */
243
244 /* GDB could not find an address for the previous symbol */
245 if (!target->rtos_auto_detect) {
246 LOG_WARNING("RTOS %s not detected. (GDB could not find symbol \'%s\')", os->type->name, cur_sym);
247 goto done;
248 } else {
249 /* Autodetecting RTOS - try next RTOS */
250 if (!rtos_try_next(target)) {
251 LOG_WARNING("No RTOS could be auto-detected!");
252 goto done;
253 }
254
255 /* Next RTOS selected - invalidate current symbol */
256 cur_sym[0] = '\x00';
257 }
258 }
259 next_sym = next_symbol(os, cur_sym, addr);
260
261 if (!next_sym->symbol_name) {
262 /* No more symbols need looking up */
263
264 if (!target->rtos_auto_detect) {
265 rtos_detected = 1;
266 goto done;
267 }
268
269 if (os->type->detect_rtos(target)) {
270 LOG_INFO("Auto-detected RTOS: %s", os->type->name);
271 rtos_detected = 1;
272 goto done;
273 } else {
274 LOG_WARNING("No RTOS could be auto-detected!");
275 goto done;
276 }
277 }
278
279 if (8 + (strlen(next_sym->symbol_name) * 2) + 1 > sizeof(reply)) {
280 LOG_ERROR("ERROR: RTOS symbol '%s' name is too long for GDB!", next_sym->symbol_name);
281 goto done;
282 }
283
284 reply_len = snprintf(reply, sizeof(reply), "qSymbol:");
285 reply_len += hexify(reply + reply_len,
286 (const uint8_t *)next_sym->symbol_name, strlen(next_sym->symbol_name),
287 sizeof(reply) - reply_len);
288
289 done:
290 gdb_put_packet(connection, reply, reply_len);
291 return rtos_detected;
292 }
293
294 int rtos_thread_packet(struct connection *connection, char const *packet, int packet_size)
295 {
296 struct target *target = get_target_from_connection(connection);
297
298 if (strncmp(packet, "qThreadExtraInfo,", 17) == 0) {
299 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL) &&
300 (target->rtos->thread_count != 0)) {
301 threadid_t threadid = 0;
302 int found = -1;
303 sscanf(packet, "qThreadExtraInfo,%" SCNx64, &threadid);
304
305 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
306 int thread_num;
307 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
308 if (target->rtos->thread_details[thread_num].threadid == threadid) {
309 if (target->rtos->thread_details[thread_num].exists)
310 found = thread_num;
311 }
312 }
313 }
314 if (found == -1) {
315 gdb_put_packet(connection, "E01", 3); /* thread not found */
316 return ERROR_OK;
317 }
318
319 struct thread_detail *detail = &target->rtos->thread_details[found];
320
321 int str_size = 0;
322 if (detail->thread_name_str != NULL)
323 str_size += strlen(detail->thread_name_str);
324 if (detail->extra_info_str != NULL)
325 str_size += strlen(detail->extra_info_str);
326
327 char *tmp_str = calloc(str_size + 9, sizeof(char));
328 char *tmp_str_ptr = tmp_str;
329
330 if (detail->thread_name_str != NULL)
331 tmp_str_ptr += sprintf(tmp_str_ptr, "Name: %s", detail->thread_name_str);
332 if (detail->extra_info_str != NULL) {
333 if (tmp_str_ptr != tmp_str)
334 tmp_str_ptr += sprintf(tmp_str_ptr, ", ");
335 tmp_str_ptr += sprintf(tmp_str_ptr, "%s", detail->extra_info_str);
336 }
337
338 assert(strlen(tmp_str) ==
339 (size_t) (tmp_str_ptr - tmp_str));
340
341 char *hex_str = malloc(strlen(tmp_str) * 2 + 1);
342 size_t pkt_len = hexify(hex_str, (const uint8_t *)tmp_str,
343 strlen(tmp_str), strlen(tmp_str) * 2 + 1);
344
345 gdb_put_packet(connection, hex_str, pkt_len);
346 free(hex_str);
347 free(tmp_str);
348 return ERROR_OK;
349
350 }
351 gdb_put_packet(connection, "", 0);
352 return ERROR_OK;
353 } else if (strncmp(packet, "qSymbol", 7) == 0) {
354 if (rtos_qsymbol(connection, packet, packet_size) == 1) {
355 if (target->rtos_auto_detect == true) {
356 target->rtos_auto_detect = false;
357 target->rtos->type->create(target);
358 }
359 target->rtos->type->update_threads(target->rtos);
360 }
361 return ERROR_OK;
362 } else if (strncmp(packet, "qfThreadInfo", 12) == 0) {
363 int i;
364 if (target->rtos != NULL) {
365 if (target->rtos->thread_count == 0) {
366 gdb_put_packet(connection, "l", 1);
367 } else {
368 /*thread id are 16 char +1 for ',' */
369 char *out_str = malloc(17 * target->rtos->thread_count + 1);
370 char *tmp_str = out_str;
371 for (i = 0; i < target->rtos->thread_count; i++) {
372 tmp_str += sprintf(tmp_str, "%c%016" PRIx64, i == 0 ? 'm' : ',',
373 target->rtos->thread_details[i].threadid);
374 }
375 gdb_put_packet(connection, out_str, strlen(out_str));
376 free(out_str);
377 }
378 } else
379 gdb_put_packet(connection, "l", 1);
380
381 return ERROR_OK;
382 } else if (strncmp(packet, "qsThreadInfo", 12) == 0) {
383 gdb_put_packet(connection, "l", 1);
384 return ERROR_OK;
385 } else if (strncmp(packet, "qAttached", 9) == 0) {
386 gdb_put_packet(connection, "1", 1);
387 return ERROR_OK;
388 } else if (strncmp(packet, "qOffsets", 8) == 0) {
389 char offsets[] = "Text=0;Data=0;Bss=0";
390 gdb_put_packet(connection, offsets, sizeof(offsets)-1);
391 return ERROR_OK;
392 } else if (strncmp(packet, "qCRC:", 5) == 0) {
393 /* make sure we check this before "qC" packet below
394 * otherwise it gets incorrectly handled */
395 return GDB_THREAD_PACKET_NOT_CONSUMED;
396 } else if (strncmp(packet, "qC", 2) == 0) {
397 if (target->rtos != NULL) {
398 char buffer[19];
399 int size;
400 size = snprintf(buffer, 19, "QC%016" PRIx64, target->rtos->current_thread);
401 gdb_put_packet(connection, buffer, size);
402 } else
403 gdb_put_packet(connection, "QC0", 3);
404 return ERROR_OK;
405 } else if (packet[0] == 'T') { /* Is thread alive? */
406 threadid_t threadid;
407 int found = -1;
408 sscanf(packet, "T%" SCNx64, &threadid);
409 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
410 int thread_num;
411 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
412 if (target->rtos->thread_details[thread_num].threadid == threadid) {
413 if (target->rtos->thread_details[thread_num].exists)
414 found = thread_num;
415 }
416 }
417 }
418 if (found != -1)
419 gdb_put_packet(connection, "OK", 2); /* thread alive */
420 else
421 gdb_put_packet(connection, "E01", 3); /* thread not found */
422 return ERROR_OK;
423 } else if (packet[0] == 'H') { /* Set current thread ( 'c' for step and continue, 'g' for
424 * all other operations ) */
425 if ((packet[1] == 'g') && (target->rtos != NULL)) {
426 threadid_t threadid;
427 sscanf(packet, "Hg%16" SCNx64, &threadid);
428 LOG_DEBUG("RTOS: GDB requested to set current thread to 0x%" PRIx64, threadid);
429 /* threadid of 0 indicates target should choose */
430 if (threadid == 0)
431 target->rtos->current_threadid = target->rtos->current_thread;
432 else
433 target->rtos->current_threadid = threadid;
434 }
435 gdb_put_packet(connection, "OK", 2);
436 return ERROR_OK;
437 }
438
439 return GDB_THREAD_PACKET_NOT_CONSUMED;
440 }
441
442 static int rtos_put_gdb_reg_list(struct connection *connection,
443 struct rtos_reg *reg_list, int num_regs)
444 {
445 size_t num_bytes = 1; /* NUL */
446 for (int i = 0; i < num_regs; ++i)
447 num_bytes += DIV_ROUND_UP(reg_list[i].size, 8) * 2;
448
449 char *hex = malloc(num_bytes);
450 char *hex_p = hex;
451
452 for (int i = 0; i < num_regs; ++i) {
453 size_t count = DIV_ROUND_UP(reg_list[i].size, 8);
454 size_t n = hexify(hex_p, reg_list[i].value, count, num_bytes);
455 hex_p += n;
456 num_bytes -= n;
457 }
458
459 gdb_put_packet(connection, hex, strlen(hex));
460 free(hex);
461
462 return ERROR_OK;
463 }
464
465 /** Look through all registers to find this register. */
466 int rtos_get_gdb_reg(struct connection *connection, int reg_num)
467 {
468 struct target *target = get_target_from_connection(connection);
469 int64_t current_threadid = target->rtos->current_threadid;
470 if ((target->rtos != NULL) && (current_threadid != -1) &&
471 (current_threadid != 0) &&
472 ((current_threadid != target->rtos->current_thread) ||
473 (target->smp))) { /* in smp several current thread are possible */
474 struct rtos_reg *reg_list;
475 int num_regs;
476
477 LOG_DEBUG("getting register %d for thread 0x%" PRIx64
478 ", target->rtos->current_thread=0x%" PRIx64,
479 reg_num,
480 current_threadid,
481 target->rtos->current_thread);
482
483 int retval;
484 if (target->rtos->type->get_thread_reg) {
485 reg_list = calloc(1, sizeof(*reg_list));
486 num_regs = 1;
487 retval = target->rtos->type->get_thread_reg(target->rtos,
488 current_threadid, reg_num, &reg_list[0]);
489 if (retval != ERROR_OK) {
490 LOG_ERROR("RTOS: failed to get register %d", reg_num);
491 return retval;
492 }
493 } else {
494 retval = target->rtos->type->get_thread_reg_list(target->rtos,
495 current_threadid,
496 &reg_list,
497 &num_regs);
498 if (retval != ERROR_OK) {
499 LOG_ERROR("RTOS: failed to get register list");
500 return retval;
501 }
502 }
503
504 for (int i = 0; i < num_regs; ++i) {
505 if (reg_list[i].number == (uint32_t)reg_num) {
506 rtos_put_gdb_reg_list(connection, reg_list + i, 1);
507 free(reg_list);
508 return ERROR_OK;
509 }
510 }
511
512 free(reg_list);
513 }
514 return ERROR_FAIL;
515 }
516
517 /** Return a list of general registers. */
518 int rtos_get_gdb_reg_list(struct connection *connection)
519 {
520 struct target *target = get_target_from_connection(connection);
521 int64_t current_threadid = target->rtos->current_threadid;
522 if ((target->rtos != NULL) && (current_threadid != -1) &&
523 (current_threadid != 0) &&
524 ((current_threadid != target->rtos->current_thread) ||
525 (target->smp))) { /* in smp several current thread are possible */
526 struct rtos_reg *reg_list;
527 int num_regs;
528
529 LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
530 ", target->rtos->current_thread=0x%" PRIx64 "\r\n",
531 current_threadid,
532 target->rtos->current_thread);
533
534 int retval = target->rtos->type->get_thread_reg_list(target->rtos,
535 current_threadid,
536 &reg_list,
537 &num_regs);
538 if (retval != ERROR_OK) {
539 LOG_ERROR("RTOS: failed to get register list");
540 return retval;
541 }
542
543 rtos_put_gdb_reg_list(connection, reg_list, num_regs);
544 free(reg_list);
545
546 return ERROR_OK;
547 }
548 return ERROR_FAIL;
549 }
550
551 int rtos_set_reg(struct connection *connection, int reg_num,
552 uint8_t *reg_value)
553 {
554 struct target *target = get_target_from_connection(connection);
555 int64_t current_threadid = target->rtos->current_threadid;
556 if ((target->rtos != NULL) &&
557 (target->rtos->type->set_reg != NULL) &&
558 (current_threadid != -1) &&
559 (current_threadid != 0)) {
560 return target->rtos->type->set_reg(target->rtos, reg_num, reg_value);
561 }
562 return ERROR_FAIL;
563 }
564
565 int rtos_generic_stack_read(struct target *target,
566 const struct rtos_register_stacking *stacking,
567 int64_t stack_ptr,
568 struct rtos_reg **reg_list,
569 int *num_regs)
570 {
571 int retval;
572
573 if (stack_ptr == 0) {
574 LOG_ERROR("Error: null stack pointer in thread");
575 return -5;
576 }
577 /* Read the stack */
578 uint8_t *stack_data = malloc(stacking->stack_registers_size);
579 uint32_t address = stack_ptr;
580
581 if (stacking->stack_growth_direction == 1)
582 address -= stacking->stack_registers_size;
583 retval = target_read_buffer(target, address, stacking->stack_registers_size, stack_data);
584 if (retval != ERROR_OK) {
585 free(stack_data);
586 LOG_ERROR("Error reading stack frame from thread");
587 return retval;
588 }
589 LOG_DEBUG("RTOS: Read stack frame at 0x%" PRIx32, address);
590
591 #if 0
592 LOG_OUTPUT("Stack Data :");
593 for (i = 0; i < stacking->stack_registers_size; i++)
594 LOG_OUTPUT("%02X", stack_data[i]);
595 LOG_OUTPUT("\r\n");
596 #endif
597
598 int64_t new_stack_ptr;
599 if (stacking->calculate_process_stack != NULL) {
600 new_stack_ptr = stacking->calculate_process_stack(target,
601 stack_data, stacking, stack_ptr);
602 } else {
603 new_stack_ptr = stack_ptr - stacking->stack_growth_direction *
604 stacking->stack_registers_size;
605 }
606
607 *reg_list = calloc(stacking->num_output_registers, sizeof(struct rtos_reg));
608 *num_regs = stacking->num_output_registers;
609
610 for (int i = 0; i < stacking->num_output_registers; ++i) {
611 (*reg_list)[i].number = stacking->register_offsets[i].number;
612 (*reg_list)[i].size = stacking->register_offsets[i].width_bits;
613
614 int offset = stacking->register_offsets[i].offset;
615 if (offset == -2)
616 buf_cpy(&new_stack_ptr, (*reg_list)[i].value, (*reg_list)[i].size);
617 else if (offset != -1)
618 buf_cpy(stack_data + offset, (*reg_list)[i].value, (*reg_list)[i].size);
619 }
620
621 free(stack_data);
622 /* LOG_OUTPUT("Output register string: %s\r\n", *hex_reg_list); */
623 return ERROR_OK;
624 }
625
626 int rtos_try_next(struct target *target)
627 {
628 struct rtos *os = target->rtos;
629 struct rtos_type **type = rtos_types;
630
631 if (!os)
632 return 0;
633
634 while (*type && os->type != *type)
635 type++;
636
637 if (!*type || !*(++type))
638 return 0;
639
640 os->type = *type;
641 if (os->symbols) {
642 free(os->symbols);
643 os->symbols = NULL;
644 }
645
646 return 1;
647 }
648
649 int rtos_update_threads(struct target *target)
650 {
651 if ((target->rtos != NULL) && (target->rtos->type != NULL))
652 target->rtos->type->update_threads(target->rtos);
653 return ERROR_OK;
654 }
655
656 void rtos_free_threadlist(struct rtos *rtos)
657 {
658 if (rtos->thread_details) {
659 int j;
660
661 for (j = 0; j < rtos->thread_count; j++) {
662 struct thread_detail *current_thread = &rtos->thread_details[j];
663 free(current_thread->thread_name_str);
664 free(current_thread->extra_info_str);
665 }
666 free(rtos->thread_details);
667 rtos->thread_details = NULL;
668 rtos->thread_count = 0;
669 rtos->current_threadid = -1;
670 rtos->current_thread = 0;
671 }
672 }

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)