1 // SPDX-License-Identifier: GPL-2.0-or-later
3 /***************************************************************************
4 * Copyright (C) 2011 by Broadcom Corporation *
5 * Evan Hunter - ehunter@broadcom.com *
6 ***************************************************************************/
13 #include "target/target.h"
14 #include "helper/log.h"
15 #include "helper/binarybuffer.h"
16 #include "server/gdb_server.h"
18 static const struct rtos_type
*rtos_types
[] = {
32 /* keep this as last, as it always matches with rtos auto */
37 static int rtos_try_next(struct target
*target
);
39 int rtos_smp_init(struct target
*target
)
41 if (target
->rtos
->type
->smp_init
)
42 return target
->rtos
->type
->smp_init(target
);
43 return ERROR_TARGET_INIT_FAILED
;
46 static int rtos_target_for_threadid(struct connection
*connection
, int64_t threadid
, struct target
**t
)
48 struct target
*curr
= get_target_from_connection(connection
);
55 static int os_alloc(struct target
*target
, const struct rtos_type
*ostype
)
57 struct rtos
*os
= target
->rtos
= calloc(1, sizeof(struct rtos
));
63 os
->current_threadid
= -1;
64 os
->current_thread
= 0;
68 /* RTOS drivers can override the packet handler in _create(). */
69 os
->gdb_thread_packet
= rtos_thread_packet
;
70 os
->gdb_target_for_threadid
= rtos_target_for_threadid
;
75 static void os_free(struct target
*target
)
80 free(target
->rtos
->symbols
);
81 rtos_free_threadlist(target
->rtos
);
86 static int os_alloc_create(struct target
*target
, const struct rtos_type
*ostype
)
88 int ret
= os_alloc(target
, ostype
);
91 ret
= target
->rtos
->type
->create(target
);
99 int rtos_create(struct jim_getopt_info
*goi
, struct target
*target
)
106 if (!goi
->isconfigure
&& goi
->argc
!= 0) {
107 Jim_WrongNumArgs(goi
->interp
, goi
->argc
, goi
->argv
, "NO PARAMS");
113 e
= jim_getopt_string(goi
, &cp
, NULL
);
117 if (strcmp(cp
, "none") == 0)
120 if (strcmp(cp
, "auto") == 0) {
121 /* Auto detect tries to look up all symbols for each RTOS,
122 * and runs the RTOS driver's _detect() function when GDB
123 * finds all symbols for any RTOS. See rtos_qsymbol(). */
124 target
->rtos_auto_detect
= true;
126 /* rtos_qsymbol() will iterate over all RTOSes. Allocate
127 * target->rtos here, and set it to the first RTOS type. */
128 return os_alloc(target
, rtos_types
[0]);
131 for (x
= 0; rtos_types
[x
]; x
++)
132 if (strcmp(cp
, rtos_types
[x
]->name
) == 0)
133 return os_alloc_create(target
, rtos_types
[x
]);
135 Jim_SetResultFormatted(goi
->interp
, "Unknown RTOS type %s, try one of: ", cp
);
136 res
= Jim_GetResult(goi
->interp
);
137 for (x
= 0; rtos_types
[x
]; x
++)
138 Jim_AppendStrings(goi
->interp
, res
, rtos_types
[x
]->name
, ", ", NULL
);
139 Jim_AppendStrings(goi
->interp
, res
, ", auto or none", NULL
);
144 void rtos_destroy(struct target
*target
)
149 int gdb_thread_packet(struct connection
*connection
, char const *packet
, int packet_size
)
151 struct target
*target
= get_target_from_connection(connection
);
153 return rtos_thread_packet(connection
, packet
, packet_size
); /* thread not
155 return target
->rtos
->gdb_thread_packet(connection
, packet
, packet_size
);
158 static struct symbol_table_elem
*find_symbol(const struct rtos
*os
, const char *symbol
)
160 struct symbol_table_elem
*s
;
162 for (s
= os
->symbols
; s
->symbol_name
; s
++)
163 if (!strcmp(s
->symbol_name
, symbol
))
169 static struct symbol_table_elem
*next_symbol(struct rtos
*os
, char *cur_symbol
, uint64_t cur_addr
)
172 os
->type
->get_symbol_list_to_lookup(&os
->symbols
);
175 return &os
->symbols
[0];
177 struct symbol_table_elem
*s
= find_symbol(os
, cur_symbol
);
181 s
->address
= cur_addr
;
186 /* rtos_qsymbol() processes and replies to all qSymbol packets from GDB.
188 * GDB sends a qSymbol:: packet (empty address, empty name) to notify
189 * that it can now answer qSymbol::hexcodedname queries, to look up symbols.
191 * If the qSymbol packet has no address that means GDB did not find the
192 * symbol, in which case auto-detect will move on to try the next RTOS.
194 * rtos_qsymbol() then calls the next_symbol() helper function, which
195 * iterates over symbol names for the current RTOS until it finds the
196 * symbol in the received GDB packet, and then returns the next entry
197 * in the list of symbols.
199 * If GDB replied about the last symbol for the RTOS and the RTOS was
200 * specified explicitly, then no further symbol lookup is done. When
201 * auto-detecting, the RTOS driver _detect() function must return success.
203 * The symbol is tried twice to handle the -flto case with gcc. The first
204 * attempt uses the symbol as-is, and the second attempt tries the symbol
205 * with ".lto_priv.0" appended to it. We only consider the first static
206 * symbol here from the -flto case. (Each subsequent static symbol with
207 * the same name is exported as .lto_priv.1, .lto_priv.2, etc.)
209 * rtos_qsymbol() returns 1 if an RTOS has been detected, or 0 otherwise.
211 int rtos_qsymbol(struct connection
*connection
, char const *packet
, int packet_size
)
213 int rtos_detected
= 0;
216 char reply
[GDB_BUFFER_SIZE
+ 1], cur_sym
[GDB_BUFFER_SIZE
/ 2 + 1] = ""; /* Extra byte for null-termination */
217 struct symbol_table_elem
*next_sym
= NULL
;
218 struct target
*target
= get_target_from_connection(connection
);
219 struct rtos
*os
= target
->rtos
;
221 reply_len
= sprintf(reply
, "OK");
226 /* Decode any symbol name in the packet*/
227 size_t len
= unhexify((uint8_t *)cur_sym
, strchr(packet
+ 8, ':') + 1, strlen(strchr(packet
+ 8, ':') + 1));
230 const char no_suffix
[] = "";
231 const char lto_suffix
[] = ".lto_priv.0";
232 const size_t lto_suffix_len
= strlen(lto_suffix
);
234 const char *cur_suffix
;
235 const char *next_suffix
;
237 /* Detect what suffix was used during the previous symbol lookup attempt, and
238 * speculatively determine the next suffix (only used for the unknown address case) */
239 if (len
> lto_suffix_len
&& !strcmp(cur_sym
+ len
- lto_suffix_len
, lto_suffix
)) {
240 /* Trim the suffix from cur_sym for comparison purposes below */
241 cur_sym
[len
- lto_suffix_len
] = '\0';
242 cur_suffix
= lto_suffix
;
245 cur_suffix
= no_suffix
;
246 next_suffix
= lto_suffix
;
249 if ((strcmp(packet
, "qSymbol::") != 0) && /* GDB is not offering symbol lookup for the first time */
250 (!sscanf(packet
, "qSymbol:%" SCNx64
":", &addr
))) { /* GDB did not find an address for a symbol */
252 /* GDB could not find an address for the previous symbol */
253 struct symbol_table_elem
*sym
= find_symbol(os
, cur_sym
);
257 } else if (sym
&& !sym
->optional
) { /* the symbol is mandatory for this RTOS */
258 if (!target
->rtos_auto_detect
) {
259 LOG_WARNING("RTOS %s not detected. (GDB could not find symbol \'%s\')", os
->type
->name
, cur_sym
);
262 /* Autodetecting RTOS - try next RTOS */
263 if (!rtos_try_next(target
)) {
264 LOG_WARNING("No RTOS could be auto-detected!");
268 /* Next RTOS selected - invalidate current symbol */
274 LOG_DEBUG("RTOS: Address of symbol '%s%s' is 0x%" PRIx64
, cur_sym
, cur_suffix
, addr
);
277 next_sym
= next_symbol(os
, cur_sym
, addr
);
278 next_suffix
= no_suffix
;
281 /* Should never happen unless the debugger misbehaves */
283 LOG_WARNING("RTOS: Debugger sent us qSymbol with '%s%s' that we did not ask for", cur_sym
, cur_suffix
);
287 if (!next_sym
->symbol_name
) {
288 /* No more symbols need looking up */
290 if (!target
->rtos_auto_detect
) {
295 if (os
->type
->detect_rtos(target
)) {
296 LOG_INFO("Auto-detected RTOS: %s", os
->type
->name
);
300 LOG_WARNING("No RTOS could be auto-detected!");
307 reply_len
= 8; /* snprintf(..., "qSymbol:") */
308 reply_len
+= 2 * strlen(next_sym
->symbol_name
); /* hexify(..., next_sym->symbol_name, ...) */
309 reply_len
+= 2 * strlen(next_suffix
); /* hexify(..., next_suffix, ...) */
310 reply_len
+= 1; /* Terminating NUL */
311 if (reply_len
> sizeof(reply
)) {
312 LOG_ERROR("ERROR: RTOS symbol '%s%s' name is too long for GDB!", next_sym
->symbol_name
, next_suffix
);
316 LOG_DEBUG("RTOS: Requesting symbol lookup of '%s%s' from the debugger", next_sym
->symbol_name
, next_suffix
);
318 reply_len
= snprintf(reply
, sizeof(reply
), "qSymbol:");
319 reply_len
+= hexify(reply
+ reply_len
,
320 (const uint8_t *)next_sym
->symbol_name
, strlen(next_sym
->symbol_name
),
321 sizeof(reply
) - reply_len
);
322 reply_len
+= hexify(reply
+ reply_len
,
323 (const uint8_t *)next_suffix
, strlen(next_suffix
),
324 sizeof(reply
) - reply_len
);
327 gdb_put_packet(connection
, reply
, reply_len
);
328 return rtos_detected
;
331 int rtos_thread_packet(struct connection
*connection
, char const *packet
, int packet_size
)
333 struct target
*target
= get_target_from_connection(connection
);
335 if (strncmp(packet
, "qThreadExtraInfo,", 17) == 0) {
336 if ((target
->rtos
) && (target
->rtos
->thread_details
) &&
337 (target
->rtos
->thread_count
!= 0)) {
338 threadid_t threadid
= 0;
340 sscanf(packet
, "qThreadExtraInfo,%" SCNx64
, &threadid
);
342 if ((target
->rtos
) && (target
->rtos
->thread_details
)) {
344 for (thread_num
= 0; thread_num
< target
->rtos
->thread_count
; thread_num
++) {
345 if (target
->rtos
->thread_details
[thread_num
].threadid
== threadid
) {
346 if (target
->rtos
->thread_details
[thread_num
].exists
)
352 gdb_put_packet(connection
, "E01", 3); /* thread not found */
356 struct thread_detail
*detail
= &target
->rtos
->thread_details
[found
];
359 if (detail
->thread_name_str
)
360 str_size
+= strlen(detail
->thread_name_str
);
361 if (detail
->extra_info_str
)
362 str_size
+= strlen(detail
->extra_info_str
);
364 char *tmp_str
= calloc(str_size
+ 9, sizeof(char));
365 char *tmp_str_ptr
= tmp_str
;
367 if (detail
->thread_name_str
)
368 tmp_str_ptr
+= sprintf(tmp_str_ptr
, "Name: %s", detail
->thread_name_str
);
369 if (detail
->extra_info_str
) {
370 if (tmp_str_ptr
!= tmp_str
)
371 tmp_str_ptr
+= sprintf(tmp_str_ptr
, ", ");
372 tmp_str_ptr
+= sprintf(tmp_str_ptr
, "%s", detail
->extra_info_str
);
375 assert(strlen(tmp_str
) ==
376 (size_t) (tmp_str_ptr
- tmp_str
));
378 char *hex_str
= malloc(strlen(tmp_str
) * 2 + 1);
379 size_t pkt_len
= hexify(hex_str
, (const uint8_t *)tmp_str
,
380 strlen(tmp_str
), strlen(tmp_str
) * 2 + 1);
382 gdb_put_packet(connection
, hex_str
, pkt_len
);
388 gdb_put_packet(connection
, "", 0);
390 } else if (strncmp(packet
, "qSymbol", 7) == 0) {
391 if (rtos_qsymbol(connection
, packet
, packet_size
) == 1) {
392 if (target
->rtos_auto_detect
== true) {
393 target
->rtos_auto_detect
= false;
394 target
->rtos
->type
->create(target
);
396 target
->rtos
->type
->update_threads(target
->rtos
);
399 } else if (strncmp(packet
, "qfThreadInfo", 12) == 0) {
402 if (target
->rtos
->thread_count
== 0) {
403 gdb_put_packet(connection
, "l", 1);
405 /*thread id are 16 char +1 for ',' */
406 char *out_str
= malloc(17 * target
->rtos
->thread_count
+ 1);
407 char *tmp_str
= out_str
;
408 for (i
= 0; i
< target
->rtos
->thread_count
; i
++) {
409 tmp_str
+= sprintf(tmp_str
, "%c%016" PRIx64
, i
== 0 ? 'm' : ',',
410 target
->rtos
->thread_details
[i
].threadid
);
412 gdb_put_packet(connection
, out_str
, strlen(out_str
));
416 gdb_put_packet(connection
, "l", 1);
419 } else if (strncmp(packet
, "qsThreadInfo", 12) == 0) {
420 gdb_put_packet(connection
, "l", 1);
422 } else if (strncmp(packet
, "qAttached", 9) == 0) {
423 gdb_put_packet(connection
, "1", 1);
425 } else if (strncmp(packet
, "qOffsets", 8) == 0) {
426 char offsets
[] = "Text=0;Data=0;Bss=0";
427 gdb_put_packet(connection
, offsets
, sizeof(offsets
)-1);
429 } else if (strncmp(packet
, "qCRC:", 5) == 0) {
430 /* make sure we check this before "qC" packet below
431 * otherwise it gets incorrectly handled */
432 return GDB_THREAD_PACKET_NOT_CONSUMED
;
433 } else if (strncmp(packet
, "qC", 2) == 0) {
437 size
= snprintf(buffer
, 19, "QC%016" PRIx64
, target
->rtos
->current_thread
);
438 gdb_put_packet(connection
, buffer
, size
);
440 gdb_put_packet(connection
, "QC0", 3);
442 } else if (packet
[0] == 'T') { /* Is thread alive? */
445 sscanf(packet
, "T%" SCNx64
, &threadid
);
446 if ((target
->rtos
) && (target
->rtos
->thread_details
)) {
448 for (thread_num
= 0; thread_num
< target
->rtos
->thread_count
; thread_num
++) {
449 if (target
->rtos
->thread_details
[thread_num
].threadid
== threadid
) {
450 if (target
->rtos
->thread_details
[thread_num
].exists
)
456 gdb_put_packet(connection
, "OK", 2); /* thread alive */
458 gdb_put_packet(connection
, "E01", 3); /* thread not found */
460 } else if (packet
[0] == 'H') { /* Set current thread ( 'c' for step and continue, 'g' for
461 * all other operations ) */
462 if ((packet
[1] == 'g') && (target
->rtos
)) {
464 sscanf(packet
, "Hg%16" SCNx64
, &threadid
);
465 LOG_DEBUG("RTOS: GDB requested to set current thread to 0x%" PRIx64
, threadid
);
466 /* threadid of 0 indicates target should choose */
468 target
->rtos
->current_threadid
= target
->rtos
->current_thread
;
470 target
->rtos
->current_threadid
= threadid
;
472 gdb_put_packet(connection
, "OK", 2);
476 return GDB_THREAD_PACKET_NOT_CONSUMED
;
479 static int rtos_put_gdb_reg_list(struct connection
*connection
,
480 struct rtos_reg
*reg_list
, int num_regs
)
482 size_t num_bytes
= 1; /* NUL */
483 for (int i
= 0; i
< num_regs
; ++i
)
484 num_bytes
+= DIV_ROUND_UP(reg_list
[i
].size
, 8) * 2;
486 char *hex
= malloc(num_bytes
);
489 for (int i
= 0; i
< num_regs
; ++i
) {
490 size_t count
= DIV_ROUND_UP(reg_list
[i
].size
, 8);
491 size_t n
= hexify(hex_p
, reg_list
[i
].value
, count
, num_bytes
);
496 gdb_put_packet(connection
, hex
, strlen(hex
));
502 /** Look through all registers to find this register. */
503 int rtos_get_gdb_reg(struct connection
*connection
, int reg_num
)
505 struct target
*target
= get_target_from_connection(connection
);
506 int64_t current_threadid
= target
->rtos
->current_threadid
;
507 if ((target
->rtos
) && (current_threadid
!= -1) &&
508 (current_threadid
!= 0) &&
509 ((current_threadid
!= target
->rtos
->current_thread
) ||
510 (target
->smp
))) { /* in smp several current thread are possible */
511 struct rtos_reg
*reg_list
;
514 LOG_DEBUG("getting register %d for thread 0x%" PRIx64
515 ", target->rtos->current_thread=0x%" PRIx64
,
518 target
->rtos
->current_thread
);
521 if (target
->rtos
->type
->get_thread_reg
) {
522 reg_list
= calloc(1, sizeof(*reg_list
));
524 retval
= target
->rtos
->type
->get_thread_reg(target
->rtos
,
525 current_threadid
, reg_num
, ®_list
[0]);
526 if (retval
!= ERROR_OK
) {
527 LOG_ERROR("RTOS: failed to get register %d", reg_num
);
531 retval
= target
->rtos
->type
->get_thread_reg_list(target
->rtos
,
535 if (retval
!= ERROR_OK
) {
536 LOG_ERROR("RTOS: failed to get register list");
541 for (int i
= 0; i
< num_regs
; ++i
) {
542 if (reg_list
[i
].number
== (uint32_t)reg_num
) {
543 rtos_put_gdb_reg_list(connection
, reg_list
+ i
, 1);
554 /** Return a list of general registers. */
555 int rtos_get_gdb_reg_list(struct connection
*connection
)
557 struct target
*target
= get_target_from_connection(connection
);
558 int64_t current_threadid
= target
->rtos
->current_threadid
;
559 if ((target
->rtos
) && (current_threadid
!= -1) &&
560 (current_threadid
!= 0) &&
561 ((current_threadid
!= target
->rtos
->current_thread
) ||
562 (target
->smp
))) { /* in smp several current thread are possible */
563 struct rtos_reg
*reg_list
;
566 LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
567 ", target->rtos->current_thread=0x%" PRIx64
"\r\n",
569 target
->rtos
->current_thread
);
571 int retval
= target
->rtos
->type
->get_thread_reg_list(target
->rtos
,
575 if (retval
!= ERROR_OK
) {
576 LOG_ERROR("RTOS: failed to get register list");
580 rtos_put_gdb_reg_list(connection
, reg_list
, num_regs
);
588 int rtos_set_reg(struct connection
*connection
, int reg_num
,
591 struct target
*target
= get_target_from_connection(connection
);
592 int64_t current_threadid
= target
->rtos
->current_threadid
;
593 if ((target
->rtos
) &&
594 (target
->rtos
->type
->set_reg
) &&
595 (current_threadid
!= -1) &&
596 (current_threadid
!= 0)) {
597 return target
->rtos
->type
->set_reg(target
->rtos
, reg_num
, reg_value
);
602 int rtos_generic_stack_read(struct target
*target
,
603 const struct rtos_register_stacking
*stacking
,
605 struct rtos_reg
**reg_list
,
610 if (stack_ptr
== 0) {
611 LOG_ERROR("Error: null stack pointer in thread");
615 uint8_t *stack_data
= malloc(stacking
->stack_registers_size
);
616 uint32_t address
= stack_ptr
;
618 if (stacking
->stack_growth_direction
== 1)
619 address
-= stacking
->stack_registers_size
;
620 if (stacking
->read_stack
)
621 retval
= stacking
->read_stack(target
, address
, stacking
, stack_data
);
623 retval
= target_read_buffer(target
, address
, stacking
->stack_registers_size
, stack_data
);
624 if (retval
!= ERROR_OK
) {
626 LOG_ERROR("Error reading stack frame from thread");
629 LOG_DEBUG("RTOS: Read stack frame at 0x%" PRIx32
, address
);
632 LOG_OUTPUT("Stack Data :");
633 for (i
= 0; i
< stacking
->stack_registers_size
; i
++)
634 LOG_OUTPUT("%02X", stack_data
[i
]);
638 target_addr_t new_stack_ptr
;
639 if (stacking
->calculate_process_stack
) {
640 new_stack_ptr
= stacking
->calculate_process_stack(target
,
641 stack_data
, stacking
, stack_ptr
);
643 new_stack_ptr
= stack_ptr
- stacking
->stack_growth_direction
*
644 stacking
->stack_registers_size
;
647 *reg_list
= calloc(stacking
->num_output_registers
, sizeof(struct rtos_reg
));
648 *num_regs
= stacking
->num_output_registers
;
650 for (int i
= 0; i
< stacking
->num_output_registers
; ++i
) {
651 (*reg_list
)[i
].number
= stacking
->register_offsets
[i
].number
;
652 (*reg_list
)[i
].size
= stacking
->register_offsets
[i
].width_bits
;
654 int offset
= stacking
->register_offsets
[i
].offset
;
656 buf_cpy(&new_stack_ptr
, (*reg_list
)[i
].value
, (*reg_list
)[i
].size
);
657 else if (offset
!= -1)
658 buf_cpy(stack_data
+ offset
, (*reg_list
)[i
].value
, (*reg_list
)[i
].size
);
662 /* LOG_OUTPUT("Output register string: %s\r\n", *hex_reg_list); */
666 static int rtos_try_next(struct target
*target
)
668 struct rtos
*os
= target
->rtos
;
669 const struct rtos_type
**type
= rtos_types
;
674 while (*type
&& os
->type
!= *type
)
677 if (!*type
|| !*(++type
))
688 int rtos_update_threads(struct target
*target
)
690 if ((target
->rtos
) && (target
->rtos
->type
))
691 target
->rtos
->type
->update_threads(target
->rtos
);
695 void rtos_free_threadlist(struct rtos
*rtos
)
697 if (rtos
->thread_details
) {
700 for (j
= 0; j
< rtos
->thread_count
; j
++) {
701 struct thread_detail
*current_thread
= &rtos
->thread_details
[j
];
702 free(current_thread
->thread_name_str
);
703 free(current_thread
->extra_info_str
);
705 free(rtos
->thread_details
);
706 rtos
->thread_details
= NULL
;
707 rtos
->thread_count
= 0;
708 rtos
->current_threadid
= -1;
709 rtos
->current_thread
= 0;
713 int rtos_read_buffer(struct target
*target
, target_addr_t address
,
714 uint32_t size
, uint8_t *buffer
)
716 if (target
->rtos
->type
->read_buffer
)
717 return target
->rtos
->type
->read_buffer(target
->rtos
, address
, size
, buffer
);
718 return ERROR_NOT_IMPLEMENTED
;
721 int rtos_write_buffer(struct target
*target
, target_addr_t address
,
722 uint32_t size
, const uint8_t *buffer
)
724 if (target
->rtos
->type
->write_buffer
)
725 return target
->rtos
->type
->write_buffer(target
->rtos
, address
, size
, buffer
);
726 return ERROR_NOT_IMPLEMENTED
;
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)