Snap for 6877830 from 1d490add468722c3ac22ac1a964b5ef4c031b69b to sdk-release
Change-Id: I4e0ce9d6c93cf77f2a4f3b9652f1b4f0e29899be
diff --git a/OWNERS b/OWNERS
index 88ace91..7d6099d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -11,3 +11,7 @@
# ASA
olorin@google.com
+
+# Remote Provisioning
+jbires@google.com
+swillden@google.com
diff --git a/fuzzing/orphans/pppd/Android.bp b/fuzzing/orphans/pppd/Android.bp
new file mode 100644
index 0000000..fa50408
--- /dev/null
+++ b/fuzzing/orphans/pppd/Android.bp
@@ -0,0 +1,31 @@
+cc_fuzz {
+ name: "eap_pppd_fuzz",
+
+ srcs: [
+ "eap_fuzz.proto",
+ "eap_fuzz.cc",
+ "eap_fuzz_Cproxy.c",
+ ],
+
+ static_libs: [
+ "libprotobuf-mutator",
+ "libpppd",
+ ],
+ shared_libs: [
+ "libprotobuf-cpp-full",
+ "libdl",
+ "liblog",
+ "libcutils",
+ "libcrypto",
+ ],
+
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+
+ ldflags: ["-rdynamic"],
+ required: [
+ "pppol2tp-android",
+ "pppopptp-android",
+ ],
+}
diff --git a/fuzzing/orphans/pppd/eap_fuzz.cc b/fuzzing/orphans/pppd/eap_fuzz.cc
new file mode 100644
index 0000000..5372e15
--- /dev/null
+++ b/fuzzing/orphans/pppd/eap_fuzz.cc
@@ -0,0 +1,215 @@
+#include <stdint.h>
+extern "C" {
+#include "eap_fuzz_Cproxy.h"
+}
+
+#include <src/libfuzzer/libfuzzer_macro.h>
+#include "eap_fuzz.pb.h"
+
+#define S_MALLOC(var, size) \
+do { \
+ if ((var = (uint8_t *)malloc(size)) == NULL) { \
+ return; \
+ } \
+} while(0)
+
+void write_header(uint8_t *packet, uint16_t data_size, uint8_t type)
+{
+ data_size += EAP_HEADERLEN;
+ //the packet type
+ *(packet)++ = type&0xff;
+ //id
+ *(packet)++ = 0x0;
+ //the length as big endian short
+ *(packet)++ = ((data_size >> 8)&0xff);
+ *(packet)++ = data_size&0xff;
+}
+
+DEFINE_BINARY_PROTO_FUZZER(const eap_fuzz::proto::PacketSet &packets){
+ init();
+
+ for(const eap_fuzz::proto::Packet& packet: packets.packets()){
+ uint8_t *fuzz_packet = NULL;
+ size_t packet_len = 0;
+ std::string data = "";
+ uint8_t packet_type = -1;
+ switch(packet.PacketType_case()){
+ case eap_fuzz::proto::Packet::kEapRequest: {
+ packet_type = EAP_REQUEST;
+ uint8_t eap_request_type = -1;
+ auto eap_request = packet.eap_request();
+ switch(eap_request.EapRequestType_case()){
+ case eap_fuzz::proto::EapRequest::kIdentity: {
+ eap_request_type = EAPT_IDENTITY;
+ data = eap_request.identity().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+1);
+ break;
+ }
+ case eap_fuzz::proto::EapRequest::kNotification: {
+ eap_request_type = EAPT_NOTIFICATION;
+ data = eap_request.notification().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+1);
+ break;
+ }
+ case eap_fuzz::proto::EapRequest::kMd5Chap: {
+ eap_request_type = EAPT_MD5CHAP;
+ data = eap_request.md5chap().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+1);
+ break;
+ }
+ case eap_fuzz::proto::EapRequest::kSrp: {
+ auto request_srp = eap_request.srp();
+ eap_request_type = EAPT_SRP;
+ uint8_t srp_type = -1;
+ switch(request_srp.EspMessage_case()){
+ case eap_fuzz::proto::EaptRequestSRP::kSrpChallenge:{
+ data = request_srp.srp_challenge().data();
+ srp_type = EAPSRP_CHALLENGE;
+ break;
+ }
+ case eap_fuzz::proto::EaptRequestSRP::kSrpValidator:{
+ data = request_srp.srp_validator().data();
+ srp_type = EAPSRP_SVALIDATOR;
+ break;
+ }
+ case eap_fuzz::proto::EaptRequestSRP::kSrpKey:{
+ data = request_srp.srp_key().data();
+ srp_type = EAPSRP_SKEY;
+ break;
+ }
+ case eap_fuzz::proto::EaptRequestSRP::kSrpLwreChallenge:{
+ data = request_srp.srp_lwre_challenge().data();
+ srp_type = EAPSRP_LWRECHALLENGE;
+ break;
+ }
+ case eap_fuzz::proto::EaptRequestSRP::ESPMESSAGE_NOT_SET:{
+ return;
+ }
+
+ }
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+2);
+ *(fuzz_packet+EAP_HEADERLEN+1) = srp_type;
+ packet_len++;
+ break;
+ }
+ case eap_fuzz::proto::EapRequest::EAPREQUESTTYPE_NOT_SET: {
+ return;
+ }
+ }
+ *(fuzz_packet+EAP_HEADERLEN) = eap_request_type;
+ ++packet_len;
+ break;
+ }
+
+ case eap_fuzz::proto::Packet::kEapResponse: {
+ packet_type = EAP_RESPONSE;
+ auto eap_response = packet.eap_response();
+ uint8_t eap_response_type = -1;
+ switch(eap_response.EapResponseType_case()){
+ case eap_fuzz::proto::EapResponse::kIdentity: {
+ eap_response_type = EAPT_IDENTITY;
+ data = eap_response.identity().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+1);
+ break;
+ }
+ case eap_fuzz::proto::EapResponse::kNotification: {
+ eap_response_type = EAPT_NOTIFICATION;
+ data = eap_response.notification().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+1);
+ break;
+ }
+ case eap_fuzz::proto::EapResponse::kMd5Chap: {
+ eap_response_type = EAPT_MD5CHAP;
+ data = eap_response.md5chap().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+1);
+ break;
+ }
+ case eap_fuzz::proto::EapResponse::kNak: {
+ eap_response_type = EAPT_NAK;
+ auto response_nak = eap_response.nak();
+ uint8_t nak_type = -1;
+ switch(response_nak.EaptResponseNAKType_case()){
+ case eap_fuzz::proto::EaptResponseNAK::kSrp:{
+ nak_type = EAPT_SRP;
+ break;
+
+ }
+ case eap_fuzz::proto::EaptResponseNAK::kMd5Chap:{
+ nak_type = EAPT_MD5CHAP;
+ break;
+
+ }
+ case eap_fuzz::proto::EaptResponseNAK::EAPTRESPONSENAKTYPE_NOT_SET:{
+ return;
+ }
+ }
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+2);
+ *(fuzz_packet+EAP_HEADERLEN+1) = nak_type;
+ packet_len++;
+ break;
+ }
+ case eap_fuzz::proto::EapResponse::kSrp: {
+ auto response_srp = eap_response.srp();
+ eap_response_type = EAPT_SRP;
+ uint8_t srp_type = -1;
+ switch(response_srp.EspMessage_case()){
+ case eap_fuzz::proto::EaptResponseSRP::kSrpChallenge:{
+ data = response_srp.srp_challenge().data();
+ srp_type = EAPSRP_LWRECHALLENGE;
+ break;
+ }
+ case eap_fuzz::proto::EaptResponseSRP::kSrpCvalidator:{
+ data = response_srp.srp_cvalidator().data();
+ srp_type = EAPSRP_CVALIDATOR;
+ break;
+ }
+ case eap_fuzz::proto::EaptResponseSRP::kSrpCkey:{
+ data = response_srp.srp_ckey().data();
+ srp_type = EAPSRP_CKEY;
+ break;
+ }
+ case eap_fuzz::proto::EaptResponseSRP::kSrpAck:{
+ data = response_srp.srp_ack().data();
+ srp_type = EAPSRP_ACK;
+ break;
+ }
+ case eap_fuzz::proto::EaptResponseSRP::ESPMESSAGE_NOT_SET:{
+ return;
+ }
+
+ }
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN+2);
+ *(fuzz_packet+EAP_HEADERLEN+1) = srp_type;
+ packet_len++;
+ break;
+ }
+ case eap_fuzz::proto::EapResponse::EAPRESPONSETYPE_NOT_SET: {
+ return;
+ }
+ }
+ *(fuzz_packet+EAP_HEADERLEN) = eap_response_type;
+ ++packet_len;
+ break;
+ }
+ case eap_fuzz::proto::Packet::kEapSuccess: {
+ packet_type = EAP_SUCCESS;
+ data = packet.eap_success().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN);
+ break;
+ }
+ case eap_fuzz::proto::Packet::kEapFailure: {
+ packet_type = EAP_FAILURE;
+ data = packet.eap_failure().data();
+ S_MALLOC(fuzz_packet, data.size()+EAP_HEADERLEN);
+ break;
+ }
+ case eap_fuzz::proto::Packet::PACKETTYPE_NOT_SET: {
+ return;
+ }
+ }
+ write_header(fuzz_packet, data.size()+packet_len, packet_type);
+ memcpy(fuzz_packet+EAP_HEADERLEN+packet_len, data.data(), data.size());
+ proxy_packet(fuzz_packet, data.size()+EAP_HEADERLEN+packet_len);
+ free(fuzz_packet);
+ }
+}
diff --git a/fuzzing/orphans/pppd/eap_fuzz.proto b/fuzzing/orphans/pppd/eap_fuzz.proto
new file mode 100644
index 0000000..6d41214
--- /dev/null
+++ b/fuzzing/orphans/pppd/eap_fuzz.proto
@@ -0,0 +1,133 @@
+syntax = "proto2";
+package eap_fuzz.proto;
+
+message PacketSet{
+ repeated Packet packets = 1;
+}
+
+message Packet{
+ oneof PacketType {
+ EapRequest eap_request = 1;
+ EapResponse eap_response = 2;
+ EapSuccess eap_success = 3;
+ EapFailure eap_failure = 4;
+ }
+}
+
+message EapRequest{
+ oneof EapRequestType{
+ EaptRequestIdentity identity = 1;
+ EsptRequestNotification notification = 2;
+ EaptRequestMD5Chap md5chap = 3;
+ EaptRequestSRP srp = 4;
+ }
+
+}
+
+message EaptRequestIdentity{
+ required bytes data = 1;
+}
+
+message EsptRequestNotification{
+ required bytes data = 1;
+}
+
+message EaptRequestMD5Chap{
+ required bytes data = 2;
+}
+message EaptRequestSRP{
+ oneof EspMessage {
+ EapRequestSRPChallenge srp_challenge = 1;
+ EapRequestSRPKey srp_key = 2;
+ EapRequestSRPValidator srp_validator = 3;
+ EapRequestSRPLWREChallenge srp_lwre_challenge = 4;
+ }
+}
+
+message EapRequestSRPChallenge{
+ required bytes data = 1;
+}
+
+message EapRequestSRPKey{
+ required bytes data = 1;
+}
+
+message EapRequestSRPValidator {
+ required bytes data = 1;
+}
+
+message EapRequestSRPLWREChallenge{
+ required bytes data = 1;
+}
+
+message EapResponse{
+ oneof EapResponseType{
+ EaptResponseIdentity identity = 1;
+ EsptResponseNotification notification = 2;
+ EaptResponseNAK nak = 3;
+ EaptResponseMD5Chap md5chap = 4;
+ EaptResponseSRP srp = 5;
+ }
+}
+
+message EaptResponseIdentity{
+ required bytes data = 1;
+}
+
+message EsptResponseNotification{
+ required bytes data = 1;
+}
+
+message EaptResponseNAK{
+ oneof EaptResponseNAKType{
+ EaptResponseNAKSRP srp = 1;
+ EaptResponseNAKMD5Chap md5_chap = 2;
+ }
+}
+
+
+message EaptResponseNAKSRP{
+ required bytes data = 1;
+}
+
+message EaptResponseNAKMD5Chap {
+ required bytes data = 1;
+}
+
+message EaptResponseMD5Chap {
+ required bytes data = 1;
+}
+
+message EaptResponseSRP{
+ oneof EspMessage {
+ EapResponseSRPCKey srp_ckey = 1;
+ EapResponseSRPCValidator srp_cvalidator = 2;
+ EapResponseSRPACK srp_ack = 3;
+ EapResponseSRPLWEChallenge srp_challenge = 4;
+ }
+}
+
+message EapResponseSRPCKey {
+ required bytes data = 1;
+}
+
+message EapResponseSRPCValidator{
+ required bytes data = 1;
+}
+
+message EapResponseSRPACK{
+ required bytes data = 1;
+}
+
+message EapResponseSRPLWEChallenge{
+ required bytes data = 1;
+}
+
+message EapSuccess{
+ required bytes data = 1;
+}
+
+message EapFailure{
+ required bytes data = 1;
+}
+
diff --git a/fuzzing/orphans/pppd/eap_fuzz_Cproxy.c b/fuzzing/orphans/pppd/eap_fuzz_Cproxy.c
new file mode 100644
index 0000000..e2b7d98
--- /dev/null
+++ b/fuzzing/orphans/pppd/eap_fuzz_Cproxy.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "pppd.h"
+#include "pathnames.h"
+#include "md5.h"
+#include "eap.h"
+#include "magic.h"
+
+void init()
+{
+ eap_protent.init(0);
+}
+
+
+void proxy_packet(uint8_t *data, int len)
+{
+ eap_protent.input(0, data, len);
+}
diff --git a/fuzzing/orphans/pppd/eap_fuzz_Cproxy.h b/fuzzing/orphans/pppd/eap_fuzz_Cproxy.h
new file mode 100644
index 0000000..6846f5e
--- /dev/null
+++ b/fuzzing/orphans/pppd/eap_fuzz_Cproxy.h
@@ -0,0 +1,56 @@
+//from pppd.h, can't include it directly in the fuzzer because C -> C++ issues
+#define EAP_HEADERLEN 4
+
+/* EAP message codes. */
+#define EAP_REQUEST 1
+#define EAP_RESPONSE 2
+#define EAP_SUCCESS 3
+#define EAP_FAILURE 4
+
+/* EAP types */
+#define EAPT_IDENTITY 1
+#define EAPT_NOTIFICATION 2
+#define EAPT_NAK 3 /* (response only) */
+#define EAPT_MD5CHAP 4
+#define EAPT_OTP 5 /* One-Time Password; RFC 1938 */
+#define EAPT_TOKEN 6 /* Generic Token Card */
+/* 7 and 8 are unassigned. */
+#define EAPT_RSA 9 /* RSA Public Key Authentication */
+#define EAPT_DSS 10 /* DSS Unilateral */
+#define EAPT_KEA 11 /* KEA */
+#define EAPT_KEA_VALIDATE 12 /* KEA-VALIDATE */
+#define EAPT_TLS 13 /* EAP-TLS */
+#define EAPT_DEFENDER 14 /* Defender Token (AXENT) */
+#define EAPT_W2K 15 /* Windows 2000 EAP */
+#define EAPT_ARCOT 16 /* Arcot Systems */
+#define EAPT_CISCOWIRELESS 17 /* Cisco Wireless */
+#define EAPT_NOKIACARD 18 /* Nokia IP smart card */
+#define EAPT_SRP 19 /* Secure Remote Password */
+/* 20 is deprecated */
+
+/* EAP SRP-SHA1 Subtypes */
+#define EAPSRP_CHALLENGE 1 /* Request 1 - Challenge */
+#define EAPSRP_CKEY 1 /* Response 1 - Client Key */
+#define EAPSRP_SKEY 2 /* Request 2 - Server Key */
+#define EAPSRP_CVALIDATOR 2 /* Response 2 - Client Validator */
+#define EAPSRP_SVALIDATOR 3 /* Request 3 - Server Validator */
+#define EAPSRP_ACK 3 /* Response 3 - final ack */
+#define EAPSRP_LWRECHALLENGE 4 /* Req/resp 4 - Lightweight rechal */
+
+#define SRPVAL_EBIT 0x00000001 /* Use shared key for ECP */
+
+#define SRP_PSEUDO_ID "pseudo_"
+#define SRP_PSEUDO_LEN 7
+
+#define MD5_SIGNATURE_SIZE 16
+#define MIN_CHALLENGE_LENGTH 16
+#define MAX_CHALLENGE_LENGTH 24
+
+void init();
+void proxy_packet(uint8_t *data, int len);
+
+//override output so we don't write to a broken fd
+void output (int unit, unsigned char *p, int len)
+{
+
+}
diff --git a/fuzzing/system_fuzzers/libcrypto_utils/Android.bp b/fuzzing/system_fuzzers/libcrypto_utils/Android.bp
new file mode 100644
index 0000000..4214b35
--- /dev/null
+++ b/fuzzing/system_fuzzers/libcrypto_utils/Android.bp
@@ -0,0 +1,17 @@
+cc_fuzz {
+ host_supported: true,
+ name : "libcrypto_utils_fuzzer",
+ srcs: [
+ "libcrypto_utils_fuzzer.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+ shared_libs: [
+ "libcrypto_utils",
+ "libcrypto",
+ ],
+}
+
diff --git a/fuzzing/system_fuzzers/libcrypto_utils/libcrypto_utils_fuzzer.cpp b/fuzzing/system_fuzzers/libcrypto_utils/libcrypto_utils_fuzzer.cpp
new file mode 100644
index 0000000..b14086d
--- /dev/null
+++ b/fuzzing/system_fuzzers/libcrypto_utils/libcrypto_utils_fuzzer.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <crypto_utils/android_pubkey.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <string.h>
+#include <memory>
+#include <openssl/obj_mac.h>
+#include <openssl/rsa.h>
+#include <cstdio>
+
+#define ANDROID_PUBKEY_MODULUS_SIZE_WORDS (ANDROID_PUBKEY_MODULUS_SIZE / 4)
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size) {
+ if (size < 2050) {
+ return 0;
+ }
+
+ FuzzedDataProvider fdp(data, size);
+
+ uint8_t buffer[ANDROID_PUBKEY_ENCODED_SIZE];
+ uint32_t modulus_size_words = ANDROID_PUBKEY_MODULUS_SIZE_WORDS;
+ memcpy(buffer, &modulus_size_words, sizeof(uint32_t));
+
+ uint32_t n0inv = fdp.ConsumeIntegralInRange<uint32_t>(0,2^32);
+ memcpy(buffer+sizeof(uint32_t), &n0inv, sizeof(uint32_t));
+
+ std::string s = fdp.ConsumeBytesAsString(ANDROID_PUBKEY_MODULUS_SIZE);
+ uint8_t* modulus = (uint8_t*)s.c_str();
+ memcpy(buffer+sizeof(uint32_t)*2, modulus,
+ sizeof(uint8_t)*ANDROID_PUBKEY_MODULUS_SIZE);
+
+ std::string ss = fdp.ConsumeBytesAsString(ANDROID_PUBKEY_MODULUS_SIZE);
+ uint8_t* rr = (uint8_t*)ss.c_str();
+ memcpy(buffer+sizeof(uint32_t)*2+ANDROID_PUBKEY_MODULUS_SIZE, rr,
+ sizeof(uint8_t)*ANDROID_PUBKEY_MODULUS_SIZE);
+
+ int flip = fdp.ConsumeIntegralInRange<uint32_t>(0,1);
+ buffer[ANDROID_PUBKEY_ENCODED_SIZE-1] = (uint8_t)(flip == 0 ? 3 : 65537);
+
+ RSA* new_key = nullptr;
+ android_pubkey_decode(buffer, sizeof(uint8_t)*ANDROID_PUBKEY_ENCODED_SIZE, &new_key);
+
+ uint8_t key_data[ANDROID_PUBKEY_ENCODED_SIZE];
+ android_pubkey_encode(new_key, key_data, sizeof(key_data));
+
+ assert(0 == memcmp(buffer, key_data, sizeof(buffer)));
+
+ RSA_free(new_key);
+
+ return 0;
+}
diff --git a/gdb/gdb_json_printer/README.md b/gdb/gdb_json_printer/README.md
new file mode 100644
index 0000000..8e4636f
--- /dev/null
+++ b/gdb/gdb_json_printer/README.md
@@ -0,0 +1,77 @@
+# gdb-json-pretty-printer
+This is a printer that prints variables in json format.
+## Install in GDB
+Copy the content in gdbinit into your ~/.gdbinit, replace the path <path_to_gdb_json_printer> by the actual path of gdb_json_printer directory.
+## Run tests
+First, in the gdb_json_printer directory, compile each example program with O0 optimization
+```
+g++ test_examples/basic_types.cpp -O0 -g -o test_examples/basic_types
+g++ test_examples/objects1.cpp -O0 -g -o test_examples/objects1
+g++ test_examples/objects2.cpp -O0 -g -o test_examples/objects2
+g++ test_examples/objects.cpp -O0 -g -o test_examples/objects
+g++ test_examples/array.cpp -O0 -g -o test_examples/array
+g++ test_examples/reference.cpp -O0 -g -o test_examples/reference
+```
+second, run gdb in the gdb_json_printer directory:
+```
+gdb
+```
+finally, source the test script:
+```
+source test/gdb_json_printer_test.py
+```
+## printing format
+```
+Pointer := {
+ type: 'pointer',
+ ctype: ctype for pointer,
+ address: Address,
+ reference: Struct
+}
+
+Struct := {
+ type: 'struct',
+ ctype: ctype for struct,
+ address: Address,
+ fields: StructField[]
+}
+
+StructField := {
+ field: name of field,
+ field_type: 'base_class' or 'argument'
+ value: Value
+}
+
+Int := {
+ type: 'int',
+ ctype: ctype for int,
+ address: Address,
+ value: number
+}
+
+Float := {
+ type: 'float',
+ ctype: 'float' or 'double',
+ address: Address,
+ value: number
+}
+
+Enum := {
+ type: 'enum',
+ ctype: ctype for enum,
+ address: Address,
+ value: number
+}
+
+Visit variable := {
+ type: 'visited',
+ ctype: ctype for struct,
+ address: Address,
+}
+
+```
+
+## problem to solve
+* support for print parent/child class members.
+* the printer treats all arrays as pointers now. We expected it to have ability to extract array/buffer length.
+* the printer prints smart pointers into a super complex json block. Maybe we need some specific printer for these common objects.
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/gdb_json_printer/printers.py b/gdb/gdb_json_printer/gdb_json_printer/printers.py
new file mode 100644
index 0000000..167f503
--- /dev/null
+++ b/gdb/gdb_json_printer/gdb_json_printer/printers.py
@@ -0,0 +1,404 @@
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import gdb
+import gdb.printing
+import re
+from abc import ABC, abstractmethod
+import json
+
+# our code now only support Python3. In python2 we need to use intptr=long. Use this typedef
+# for easier support of Python2 later.
+intptr = int
+
+
+def check_optimized_out(to_json):
+ """ A decorator for to_json method of JsonPrinters. Check if the value in the printer
+ is already optimized out.
+ If the value is already optimized out, return {'type': 'optimized_out'}.
+
+ Args:
+ to_json: A to_json method of a JsonPrinter.
+
+ Returns:
+ decorated to_json method
+ """
+
+ def decorator(self, *args, **kwargs):
+ if self.value is None or self.value.is_optimized_out:
+ return {'type': 'optimized_out'}
+
+ return to_json(self, *args, **kwargs)
+ return decorator
+
+
+def check_visited(to_json):
+ """ A decorator for to_json method of JsonPrinters. Check if the value in the printer
+ has appeared in visited_addresses_and_types.
+ If the value has appeared in visited_addresses_and_types, return {'type': 'visited',
+ 'ctype': str(self.value.type), 'address': self.address}
+
+ Args:
+ to_json: A to_json method of a JsonPrinter.
+
+ Returns:
+ decorated to_json method
+ """
+
+ def decorator(self):
+ addresstype = self.get_address_type()
+ if addresstype != None and addresstype in self.visited_addresses_and_types:
+ return {
+ 'type': 'visited',
+ 'ctype': str(self.value.type),
+ 'address': self.address
+ }
+ self.visited_addresses_and_types.add(self.get_address_type())
+ return to_json(self)
+ return decorator
+
+
+class JsonPrinter(ABC):
+ """Base class for all json printers.
+
+ Attributes:
+ value:
+ The value to print, note that the lifetime of printer is limited to single print command.
+ visited_addresses_and_types:
+ A set of all `address`_`type` string that already visited during printing.
+ If the address and type is in the set and the value is a struct or pointer,
+ the printer will mark 'type' as visited and no longer expand the value. Can be None.
+ """
+
+ def __init__(self, value, visited_addresses_and_types=None):
+ """ Constructor. A JsonPrinter will be created for each time a variable is printed.
+
+ Args:
+ value:
+ A gdb.Value object, the value to print.
+ visited_addresses_and_types:
+ A set of all `address`_`type` string that already visited during printing.
+ If the address and type is in the set and the value is a struct or pointer,
+ the printer will mark 'type' as visited and no longer expand the value. Can be None.
+ """
+ self.value = value
+ self.visited_addresses_and_types = visited_addresses_and_types
+ if self.visited_addresses_and_types == None:
+ self.visited_addresses_and_types = set()
+
+ # value may not have address attribute if it's in register
+ if hasattr(self.value, 'address') and self.value.address is not None:
+ self.address = hex(intptr(self.value.address))
+ else:
+ self.address = None
+
+ def to_string(self):
+ """ Gdb Python interface to print a gdb.Value.
+
+ Returns:
+ A string representing the json format of a gdb.Value.
+ """
+ return json.dumps(self.to_json(), indent=4)
+
+ @abstractmethod
+ def to_json(self):
+ """ The method to be implemented. Convert a gdb.Value object into a Python json object.
+
+ Returns:
+ A Python json object.
+ """
+ pass
+
+ # we use address and type to identify a printed value and avoid circular references
+ def get_address_type(self):
+ address_str = self.address if self.address is not None else ""
+ return address_str + "_" + str(self.value.type.strip_typedefs())
+
+
+class BasicTypePrinter(JsonPrinter):
+ """ Printer for basic types, now supports Enum, Int, Float, and Void.
+
+ All integer variables in C++ will be considered as Int, e.g. char, short, long. Similarly,
+ float and double are all considered as Float. For enum variables, they are actually int in
+ C++, gdb knows they are enum variables, but can only print them as integer.
+ """
+
+ """
+ BasicTypePrinter uses this dict to print basic_types.
+ """
+ basic_type_json_info = {
+ gdb.TYPE_CODE_ENUM: {'type': 'enum', 'value_func': lambda value: str(int(value))},
+ gdb.TYPE_CODE_INT: {'type': 'int', 'value_func': lambda value: str(int(value))},
+ gdb.TYPE_CODE_FLT: {'type': 'float', 'value_func': lambda value: str(float(value))},
+ gdb.TYPE_CODE_VOID: {'type': 'void', 'value_func': lambda value: None},
+ }
+
+ @check_optimized_out
+ @check_visited
+ def to_json(self):
+ """ Output format is:
+ {
+ 'type': 'int'/'float'/'enum'/'void',
+ 'ctype': string format type in C++,
+ 'address': string format address,
+ 'value': string format value
+ }.
+ """
+ type_code = self.value.type.strip_typedefs().code
+ json_type = BasicTypePrinter.basic_type_json_info[type_code]["type"]
+ value_func = BasicTypePrinter.basic_type_json_info[type_code]["value_func"]
+ value_json = {
+ 'type': json_type,
+ 'ctype': str(self.value.type),
+ 'address': self.address,
+ 'value': value_func(self.value)
+ }
+ return value_json
+
+
+class ObjectPrinter(JsonPrinter):
+ """ A Printer for objects in C++.
+
+ The current version won't extract the dynamic/actual type of objects, and the member variable
+ of a parent/child class won't be printed. We expect to support this function later.
+ """
+
+ @check_visited
+ def to_json_without_expanding_base_class(self):
+ """ A helper function for the to_json method.
+
+ It tries to extract all member variables of an object without casting it into base classes.
+ """
+ value_json = {
+ 'type': 'struct',
+ 'ctype': str(self.value.type),
+ 'address': self.address,
+ 'fields': []
+ }
+
+ for field in self.value.type.fields():
+ if not field.is_base_class:
+ field_json = {
+ 'field': field.name,
+ 'value': None
+ }
+
+ field_printer = general_lookup_function(
+ self.value[field.name], self.visited_addresses_and_types)
+ try:
+ field_json['value'] = field_printer.to_json()
+ except:
+ field_json['value'] = "extract failed"
+
+ value_json['fields'].append(field_json)
+
+ return value_json
+
+ @check_optimized_out
+ def to_json(self, cast_to_dynamic_type=True):
+ """Output format:
+ {
+ 'type': 'struct',
+ 'ctype': string format type in C++,
+ 'address': string format address,
+ 'base_classes': [] # a list of value casted into each base class
+ 'fields': [] # a list of struct fields
+ }.
+ For each field in fields, its format is:
+ {
+ 'field': string format of field name,
+ 'field_type': 'base_class'/'member', if it is a base class. the value will be the
+ object which is casted into that base_class. Otherwise, the value is the json of
+ the member variable.
+ 'value': json of the field
+ }.
+ """
+ if cast_to_dynamic_type and \
+ self.value.type.strip_typedefs() != self.value.dynamic_type.strip_typedefs():
+ self.value = self.value.cast(self.value.dynamic_type)
+
+ # address/type pair is set to visited after casted to dynamic type to avoid base class
+ # being filtered by the visited check
+ value_json = self.to_json_without_expanding_base_class()
+
+ # if the type is visited, it's not necessary to explore its ancestors.
+ if value_json["type"] != "visited":
+ base_classes_list = []
+ for field in self.value.type.fields():
+ if field.is_base_class:
+ field_json = {
+ 'base_class': field.name,
+ 'value': None
+ }
+
+ base_class_printer = ObjectPrinter(
+ self.value.cast(field.type),
+ self.visited_addresses_and_types)
+ field_json["value"] = base_class_printer.to_json(
+ cast_to_dynamic_type=False)
+ base_classes_list.append(field_json)
+ value_json['base_classes'] = base_classes_list
+
+ return value_json
+
+
+class RefAndPtrPrinter(JsonPrinter):
+ """ Printer for reference and raw pointer in C++.
+ """
+
+ def __init__(self, value, visited_addresses_and_types=None):
+ super().__init__(value, visited_addresses_and_types)
+ self.void_ptr_re = re.compile(r"^.*void\s?\*$")
+
+ @check_optimized_out
+ @check_visited
+ def to_json(self):
+ """Output format:
+ {
+ 'type': 'pointer'/"reference,
+ 'ctype': string format type in C++,
+ 'address': string format address,
+ 'reference': Json for a C++ object
+ }.
+ If the pointer is a void* pointer, reference would be "cannot extract void ptr",
+ because gdb cannot extract content from a void* pointer. If the pointer is nullptr,
+ reference would be "nullptr".
+ """
+ value_type = 'pointer' if self.value.type.code == gdb.TYPE_CODE_PTR else 'reference'
+ value_json = {
+ 'type': value_type,
+ 'ctype': str(self.value.type),
+ 'address': self.address,
+ 'reference': None
+ }
+
+ # handle void pointer, dereference a void pointer will cause exception
+ if self.void_ptr_re.match(str(self.value.type.strip_typedefs())) is not None:
+ value_json['reference'] = "cannot extract void ptr"
+ return value_json
+
+ # handle nullptr
+ if value_type == 'pointer' and int(self.value) == 0:
+ value_json['reference'] = "nullptr"
+ return value_json
+
+ deref_value = self.value.referenced_value()
+ deref_value_printer = general_lookup_function(
+ deref_value, self.visited_addresses_and_types)
+ value_json['reference'] = deref_value_printer.to_json()
+ return value_json
+
+
+class ExtractFailedPrinter(JsonPrinter):
+ """ Printer for the case that cannot be extracted by current printer.
+ """
+
+ def to_json(self):
+ """ output format: {'type': 'extract failed'}
+ """
+ return {'type': 'extract failed'}
+
+
+class OptimizedOutPrinter(JsonPrinter):
+ """ Printer for the case that a variable is already optimized out.
+ """
+
+ def to_json(self):
+ """ Output format: {'type': 'optimized out'}.
+ """
+ return {'type': 'optimized out'}
+
+
+class StackArrayPrinter(JsonPrinter):
+ """ Printer for the arrays in C++.
+
+ Note that this printer works only for C++ arrays(with type= T[]). It cannot
+ print out buffers on heap.
+ Output format:
+ {
+ 'type': 'array',
+ 'element_type': string format type of array elements in C++,
+ 'values': A list of Value json indiciating each element in array
+ }.
+ """
+ @check_optimized_out
+ @check_visited
+ def to_json(self):
+ total_size = self.value.type.sizeof
+ element_size = self.value.type.target().sizeof
+ element_count = total_size // element_size
+ stack_array_json = {
+ 'type': 'array',
+ 'element_type': str(self.value.type.target()),
+ 'values': []
+ }
+ for idx in range(element_count):
+ element_json = general_lookup_function(
+ self.value[idx], self.visited_addresses_and_types).to_json()
+ stack_array_json['values'].append(element_json)
+ return stack_array_json
+
+
+""" The table indiciating which printer should be called given a gdb value.
+"""
+gdb_type_printer_map = {
+ gdb.TYPE_CODE_PTR: RefAndPtrPrinter,
+ gdb.TYPE_CODE_ARRAY: StackArrayPrinter,
+ gdb.TYPE_CODE_STRUCT: ObjectPrinter,
+ gdb.TYPE_CODE_UNION: ObjectPrinter,
+ gdb.TYPE_CODE_ENUM: BasicTypePrinter,
+ gdb.TYPE_CODE_FLAGS: None,
+ gdb.TYPE_CODE_FUNC: None,
+ gdb.TYPE_CODE_INT: BasicTypePrinter,
+ gdb.TYPE_CODE_FLT: BasicTypePrinter,
+ gdb.TYPE_CODE_VOID: BasicTypePrinter,
+ gdb.TYPE_CODE_SET: None, # not exist in C++
+ gdb.TYPE_CODE_RANGE: None, # not exist in C++?
+ # not exist in C++, in C++, string is not a built-in type
+ gdb.TYPE_CODE_STRING: None,
+ gdb.TYPE_CODE_BITSTRING: None, # deprecated
+ gdb.TYPE_CODE_ERROR: None,
+ gdb.TYPE_CODE_METHOD: None,
+ gdb.TYPE_CODE_METHODPTR: None,
+ gdb.TYPE_CODE_MEMBERPTR: None,
+ gdb.TYPE_CODE_REF: RefAndPtrPrinter,
+ gdb.TYPE_CODE_RVALUE_REF: None,
+ gdb.TYPE_CODE_CHAR: None, # char is an integer in C++
+ gdb.TYPE_CODE_BOOL: None, # bool is actually char in C++
+ gdb.TYPE_CODE_COMPLEX: None, # not exist in C++
+ gdb.TYPE_CODE_TYPEDEF: None,
+ gdb.TYPE_CODE_NAMESPACE: None,
+ gdb.TYPE_CODE_DECFLOAT: None, # not exist in C++
+ gdb.TYPE_CODE_INTERNAL_FUNCTION: None
+}
+
+
+def general_lookup_function(value, visited_addresses_and_types=None):
+ """ The actual printer installed, it will select JsonPrinter based on the gdb value given.
+ """
+ if value.is_optimized_out:
+ return OptimizedOutPrinter(value)
+ type_code = value.type.strip_typedefs().code
+ if type_code not in gdb_type_printer_map or gdb_type_printer_map[type_code] is None:
+ return ExtractFailedPrinter(value)
+
+ # TODO: add regex match and specific printer for some type here? such as shared_ptr
+
+ return gdb_type_printer_map[type_code](value, visited_addresses_and_types)
+
+
+def register_printers():
+ """ Call this function in your ~/.gdbinit to register the printer into gdb.
+ """
+ gdb.pretty_printers.append(general_lookup_function)
diff --git a/gdb/gdb_json_printer/gdbinit b/gdb/gdb_json_printer/gdbinit
new file mode 100644
index 0000000..1410988
--- /dev/null
+++ b/gdb/gdb_json_printer/gdbinit
@@ -0,0 +1,7 @@
+set auto-load python-scripts off
+python
+import sys
+sys.path.insert(0, '<path_to_gdb_json_printer>')
+from gdb_json_printer.printers import register_printers
+register_printers()
+end
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/array_42_a.json b/gdb/gdb_json_printer/test/array_42_a.json
new file mode 100644
index 0000000..7518900
--- /dev/null
+++ b/gdb/gdb_json_printer/test/array_42_a.json
@@ -0,0 +1,36 @@
+{
+ "type": "array",
+ "element_type": "int16_t",
+ "values": [
+ {
+ "type": "int",
+ "ctype": "int16_t",
+ "address": "0x7fffffffd766",
+ "value": "1"
+ },
+ {
+ "type": "int",
+ "ctype": "int16_t",
+ "address": "0x7fffffffd768",
+ "value": "2"
+ },
+ {
+ "type": "int",
+ "ctype": "int16_t",
+ "address": "0x7fffffffd76a",
+ "value": "3"
+ },
+ {
+ "type": "int",
+ "ctype": "int16_t",
+ "address": "0x7fffffffd76c",
+ "value": "4"
+ },
+ {
+ "type": "int",
+ "ctype": "int16_t",
+ "address": "0x7fffffffd76e",
+ "value": "5"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/array_42_b.json b/gdb/gdb_json_printer/test/array_42_b.json
new file mode 100644
index 0000000..8edf0b4
--- /dev/null
+++ b/gdb/gdb_json_printer/test/array_42_b.json
@@ -0,0 +1,11 @@
+{
+ "type": "pointer",
+ "ctype": "int16_t *",
+ "address": "0x7fffffffd778",
+ "reference": {
+ "type": "int",
+ "ctype": "int16_t",
+ "address": "0x7fffffffd76a",
+ "value": "3"
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/array_42_bar.json b/gdb/gdb_json_printer/test/array_42_bar.json
new file mode 100644
index 0000000..ed2f5be
--- /dev/null
+++ b/gdb/gdb_json_printer/test/array_42_bar.json
@@ -0,0 +1,57 @@
+{
+ "type": "array",
+ "element_type": "Bar",
+ "values": [
+ {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd72a",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd72a",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ },
+ {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd72c",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd72c",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ },
+ {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd72e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd72e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/array_42_foo.json b/gdb/gdb_json_printer/test/array_42_foo.json
new file mode 100644
index 0000000..fafd644
--- /dev/null
+++ b/gdb/gdb_json_printer/test/array_42_foo.json
@@ -0,0 +1,132 @@
+{
+ "type": "array",
+ "element_type": "Foo",
+ "values": [
+ {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd730",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd730",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd738",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd72a",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd72a",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ },
+ {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd740",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd740",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd748",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd72c",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd72c",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ },
+ {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd750",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd750",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd758",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd72e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd72e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/gdb_json_printer_test.py b/gdb/gdb_json_printer/test/gdb_json_printer_test.py
new file mode 100644
index 0000000..dd6b40e
--- /dev/null
+++ b/gdb/gdb_json_printer/test/gdb_json_printer_test.py
@@ -0,0 +1,236 @@
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+import gdb
+import json
+
+
+def remove_gdb_output_prefix(str):
+ """ gdb output contains a "$%d =" prefix, the function remove the prefix
+ """
+ idx = str.find("=")
+ return str[idx + 1:]
+
+
+def check_type_and_ctype(input_json, input_type, ctype):
+ assert(input_json["type"] == input_type)
+ assert(input_json["ctype"] == ctype)
+
+
+def check_basic_value(input_json, input_type, ctype, value):
+ check_type_and_ctype(input_json, input_type, ctype)
+ assert(input_json["value"] == value)
+
+
+def gdb_extract_variable_to_json(variable_name):
+ """ extract a variable from gdb
+
+ Args:
+ variable_name:
+ a string, the name of the variable to extract
+ Returns:
+ A json Object in Python
+ """
+ variable = gdb.execute("p {}".format(variable_name), to_string=True)
+ variable = remove_gdb_output_prefix(variable)
+ variable_json = json.loads(variable)
+ return variable_json
+
+
+def json_equal_except_address(testcase, json0, json1):
+ """ check if all content of two json are same except the address fields
+
+ The function will check the two json objects recursively to set address fields to None,
+ then convert two json objects into strings with sorted keys, and check if the two strings
+ are same.
+ """
+ def replace_address_by_None(input_json):
+ if isinstance(input_json, dict):
+ for (k, v) in input_json.items():
+ if k == "address":
+ input_json[k] = None
+ else:
+ replace_address_by_None(v)
+ elif isinstance(input_json, list):
+ for item in input_json:
+ replace_address_by_None(item)
+
+ replace_address_by_None(json0)
+ replace_address_by_None(json1)
+ json0 = json.dumps(json0, sort_keys=True)
+ json1 = json.dumps(json1, sort_keys=True)
+ testcase.assertEqual(json0, json1)
+
+
+def test_json_variable(testcase, testname, line, variable_name_list):
+ for variable_name in variable_name_list:
+ variable_json = gdb_extract_variable_to_json(variable_name)
+ with open("test/{}_{}_{}.json".format(testname, str(line), variable_name), "r") as f:
+ expect_variable = json.load(f)
+ json_equal_except_address(testcase, variable_json, expect_variable)
+
+
+class TestGdbJsonPrinter(unittest.TestCase):
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.maxDiff = None
+
+ def test_basic_types(self):
+ gdb.execute("file test_examples/basic_types")
+ gdb.execute("b 42")
+ gdb.execute("r")
+
+ variable_json = gdb_extract_variable_to_json("a")
+ check_basic_value(variable_json, "int", "uint8_t", "1")
+
+ variable_json = gdb_extract_variable_to_json("a")
+ check_basic_value(variable_json, "int", "uint8_t", "1")
+
+ variable_json = gdb_extract_variable_to_json("b")
+ check_basic_value(variable_json, "int", "int8_t", "-2")
+
+ variable_json = gdb_extract_variable_to_json("c")
+ check_basic_value(variable_json, "int", "uint16_t", "3")
+
+ variable_json = gdb_extract_variable_to_json("d")
+ check_basic_value(variable_json, "int", "int16_t", "-4")
+
+ variable_json = gdb_extract_variable_to_json("e")
+ check_basic_value(variable_json, "int", "uint32_t", "5")
+
+ variable_json = gdb_extract_variable_to_json("f")
+ check_basic_value(variable_json, "int", "int32_t", "-6")
+
+ variable_json = gdb_extract_variable_to_json("g")
+ check_basic_value(variable_json, "int", "uint64_t", "7")
+
+ variable_json = gdb_extract_variable_to_json("h")
+ check_basic_value(variable_json, "int", "int64_t", "-8")
+
+ variable_json = gdb_extract_variable_to_json("i")
+ check_basic_value(variable_json, "float", "float", "9.0")
+
+ variable_json = gdb_extract_variable_to_json("j")
+ check_basic_value(variable_json, "float", "double", "-10.0")
+
+ variable_json = gdb_extract_variable_to_json("k")
+ check_type_and_ctype(variable_json, "pointer", "const char *")
+ check_basic_value(
+ variable_json["reference"], "int", "const char", "72")
+
+ variable_json = gdb_extract_variable_to_json("l")
+ check_type_and_ctype(variable_json, "pointer", "void *")
+ assert(variable_json["reference"] == "cannot extract void ptr")
+
+ variable_json = gdb_extract_variable_to_json("m")
+ check_type_and_ctype(variable_json, "pointer", "char *")
+ assert(variable_json["reference"] == "nullptr")
+
+ variable_json = gdb_extract_variable_to_json("o")
+ check_basic_value(variable_json, "enum", "Animal", "0")
+
+ gdb.execute("b 61")
+ gdb.execute("c")
+
+ variable_json = gdb_extract_variable_to_json("a")
+ check_basic_value(variable_json, "int", "const uint8_t", "1")
+
+ variable_json = gdb_extract_variable_to_json("a")
+ check_basic_value(variable_json, "int", "const uint8_t", "1")
+
+ variable_json = gdb_extract_variable_to_json("b")
+ check_basic_value(variable_json, "int", "const int8_t", "-2")
+
+ variable_json = gdb_extract_variable_to_json("c")
+ check_basic_value(variable_json, "int", "const uint16_t", "3")
+
+ variable_json = gdb_extract_variable_to_json("d")
+ check_basic_value(variable_json, "int", "const int16_t", "-4")
+
+ variable_json = gdb_extract_variable_to_json("e")
+ check_basic_value(variable_json, "int", "const uint32_t", "5")
+
+ variable_json = gdb_extract_variable_to_json("f")
+ check_basic_value(variable_json, "int", "const int32_t", "-6")
+
+ variable_json = gdb_extract_variable_to_json("g")
+ check_basic_value(variable_json, "int", "const uint64_t", "7")
+
+ variable_json = gdb_extract_variable_to_json("h")
+ check_basic_value(variable_json, "int", "const int64_t", "-8")
+
+ variable_json = gdb_extract_variable_to_json("i")
+ check_basic_value(variable_json, "float", "const float", "9.0")
+
+ variable_json = gdb_extract_variable_to_json("j")
+ check_basic_value(variable_json, "float", "const double", "-10.0")
+
+ variable_json = gdb_extract_variable_to_json("k")
+ check_type_and_ctype(variable_json, "pointer", "const char *")
+ check_basic_value(
+ variable_json["reference"], "int", "const char", "72")
+
+ variable_json = gdb_extract_variable_to_json("l")
+ check_type_and_ctype(variable_json, "pointer", "const void *")
+ assert(variable_json["reference"] == "cannot extract void ptr")
+
+ variable_json = gdb_extract_variable_to_json("m")
+ check_type_and_ctype(variable_json, "pointer", "const char *")
+ assert(variable_json["reference"] == "nullptr")
+
+ variable_json = gdb_extract_variable_to_json("o")
+ check_basic_value(variable_json, "enum", "const Animal", "1")
+
+ def test_objects(self):
+ gdb.execute("file test_examples/objects")
+ gdb.execute("b 37")
+ gdb.execute("r")
+ test_json_variable(self, "objects", 37, ["bar", "foo"])
+
+ def test_objects1(self):
+ gdb.execute("file test_examples/objects1")
+ gdb.execute("b 41")
+ gdb.execute("r")
+ test_json_variable(self, "objects1", 41, ["bar", "foo"])
+
+ def test_objects2(self):
+ gdb.execute("file test_examples/objects2")
+ gdb.execute("b 85")
+ gdb.execute("r")
+ test_json_variable(
+ self,
+ "objects2",
+ 85,
+ ["a", "b", "c", "d", "e", "f", "w", "x", "y", "z"])
+
+ def test_array(self):
+ gdb.execute("file test_examples/array")
+ gdb.execute("b 42")
+ gdb.execute("r")
+ test_json_variable(self, "array", 42, ["a", "b", "bar", "foo"])
+
+ def test_reference(self):
+ gdb.execute("file test_examples/reference")
+ gdb.execute("b 41")
+ gdb.execute("r")
+ test_json_variable(
+ self,
+ "reference",
+ 41,
+ ["bar", "foo", "bar_ref", "foo_ref", "bar_ptr", "foo_ptr"])
+
+
+unittest.main()
diff --git a/gdb/gdb_json_printer/test/objects1_41_bar.json b/gdb/gdb_json_printer/test/objects1_41_bar.json
new file mode 100644
index 0000000..2f0cdac
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects1_41_bar.json
@@ -0,0 +1,60 @@
+{
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd760",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x55555556aed0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556aed0",
+ "value": "2"
+ }
+ },
+ {
+ "field": "foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x55555556aed8",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aeb0",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aeb0",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x55555556aeb8",
+ "reference": {
+ "type": "visited",
+ "ctype": "Bar",
+ "address": "0x55555556aed0"
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects1_41_foo.json b/gdb/gdb_json_printer/test/objects1_41_foo.json
new file mode 100644
index 0000000..6ce8164
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects1_41_foo.json
@@ -0,0 +1,60 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd768",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aeb0",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aeb0",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x55555556aeb8",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x55555556aed0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556aed0",
+ "value": "2"
+ }
+ },
+ {
+ "field": "foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x55555556aed8",
+ "reference": {
+ "type": "visited",
+ "ctype": "Foo",
+ "address": "0x55555556aeb0"
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_a.json b/gdb/gdb_json_printer/test/objects2_85_a.json
new file mode 100644
index 0000000..f6ae441
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_a.json
@@ -0,0 +1,22 @@
+{
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd748",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x55555556af29",
+ "fields": [
+ {
+ "field": "e",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556af29",
+ "value": "5"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_b.json b/gdb/gdb_json_printer/test/objects2_85_b.json
new file mode 100644
index 0000000..2a78825
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_b.json
@@ -0,0 +1,63 @@
+{
+ "type": "struct",
+ "ctype": "Foo2",
+ "address": "0x7fffffffd710",
+ "fields": [
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x7fffffffd71c",
+ "value": "4"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x7fffffffd720",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd710",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x7fffffffd710",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c70",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd718",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_c.json b/gdb/gdb_json_printer/test/objects2_85_c.json
new file mode 100644
index 0000000..41fed32
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_c.json
@@ -0,0 +1,146 @@
+{
+ "type": "reference",
+ "ctype": "Foo &",
+ "address": "0x55555556aef0",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo3",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "d",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af14",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo1",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo1",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556aefa",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556aef0",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c40",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aef8",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ },
+ {
+ "base_class": "Foo2",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo2",
+ "address": "0x55555556af00",
+ "fields": [
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af0c",
+ "value": "3"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af10",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556af00",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af00",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c58",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556af08",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_d.json b/gdb/gdb_json_printer/test/objects2_85_d.json
new file mode 100644
index 0000000..53210de
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_d.json
@@ -0,0 +1,54 @@
+{
+ "type": "struct",
+ "ctype": "Foo1",
+ "address": "0x7fffffffd700",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd70a",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd700",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x7fffffffd700",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c88",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd708",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_e.json b/gdb/gdb_json_printer/test/objects2_85_e.json
new file mode 100644
index 0000000..2617f5a
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_e.json
@@ -0,0 +1,150 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd738",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo6",
+ "address": "0x55555556af68",
+ "fields": [
+ {
+ "field": "d",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af60",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo4",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo4",
+ "address": "0x55555556af40",
+ "fields": [
+ {
+ "field": "_vptr.Foo4",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af40",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557b30",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556af48",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556af68",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af68",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557b70",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556af70",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ },
+ {
+ "base_class": "Foo5",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo5",
+ "address": "0x55555556af50",
+ "fields": [
+ {
+ "field": "_vptr.Foo5",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af50",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557b50",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af58",
+ "value": "3"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af5c",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "visited",
+ "ctype": "Foo",
+ "address": "0x55555556af68"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_f.json b/gdb/gdb_json_printer/test/objects2_85_f.json
new file mode 100644
index 0000000..8ef76cd
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_f.json
@@ -0,0 +1,150 @@
+{
+ "type": "pointer",
+ "ctype": "Foo4 *",
+ "address": "0x7fffffffd730",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo6",
+ "address": "0x55555556af40",
+ "fields": [
+ {
+ "field": "d",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af60",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo4",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo4",
+ "address": "0x55555556af40",
+ "fields": [
+ {
+ "field": "_vptr.Foo4",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af40",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557b30",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556af48",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556af68",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af68",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557b70",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556af70",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ },
+ {
+ "base_class": "Foo5",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo5",
+ "address": "0x55555556af50",
+ "fields": [
+ {
+ "field": "_vptr.Foo5",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af50",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557b50",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af58",
+ "value": "3"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af5c",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "visited",
+ "ctype": "Foo",
+ "address": "0x55555556af68"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_w.json b/gdb/gdb_json_printer/test/objects2_85_w.json
new file mode 100644
index 0000000..cf9037b
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_w.json
@@ -0,0 +1,146 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd750",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo3",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "d",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af14",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo1",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo1",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556aefa",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556aef0",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c40",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aef8",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ },
+ {
+ "base_class": "Foo2",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo2",
+ "address": "0x55555556af00",
+ "fields": [
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af0c",
+ "value": "3"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af10",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556af00",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af00",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c58",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556af08",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_x.json b/gdb/gdb_json_printer/test/objects2_85_x.json
new file mode 100644
index 0000000..a2b8fad
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_x.json
@@ -0,0 +1,59 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd768",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo1",
+ "address": "0x55555556aeb0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556aeba",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aeb0",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556aeb0",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c88",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aeb8",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_y.json b/gdb/gdb_json_printer/test/objects2_85_y.json
new file mode 100644
index 0000000..4f12cef
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_y.json
@@ -0,0 +1,68 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd760",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo2",
+ "address": "0x55555556aed0",
+ "fields": [
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556aedc",
+ "value": "3"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556aee0",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aed0",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556aed0",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c70",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aed8",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects2_85_z.json b/gdb/gdb_json_printer/test/objects2_85_z.json
new file mode 100644
index 0000000..c77c9b5
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects2_85_z.json
@@ -0,0 +1,146 @@
+{
+ "type": "pointer",
+ "ctype": "Foo1 *",
+ "address": "0x7fffffffd758",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo3",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "d",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af14",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo1",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo1",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556aefa",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556aef0",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556aef0",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c40",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556aef8",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ },
+ {
+ "base_class": "Foo2",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo2",
+ "address": "0x55555556af00",
+ "fields": [
+ {
+ "field": "c",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af0c",
+ "value": "3"
+ }
+ },
+ {
+ "field": "cc",
+ "value": {
+ "type": "int",
+ "ctype": "uint32_t",
+ "address": "0x55555556af10",
+ "value": "4"
+ }
+ }
+ ],
+ "base_classes": [
+ {
+ "base_class": "Foo",
+ "value": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556af00",
+ "fields": [
+ {
+ "field": "_vptr.Foo",
+ "value": {
+ "type": "pointer",
+ "ctype": "int (**)(void)",
+ "address": "0x55555556af00",
+ "reference": {
+ "type": "pointer",
+ "ctype": "int (*)(void)",
+ "address": "0x555555557c58",
+ "reference": {
+ "type": "extract failed"
+ }
+ }
+ }
+ },
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556af08",
+ "value": "1"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects_37_bar.json b/gdb/gdb_json_printer/test/objects_37_bar.json
new file mode 100644
index 0000000..38cb376
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects_37_bar.json
@@ -0,0 +1,22 @@
+{
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd760",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x55555556ded0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556ded0",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/objects_37_foo.json b/gdb/gdb_json_printer/test/objects_37_foo.json
new file mode 100644
index 0000000..58202f7
--- /dev/null
+++ b/gdb/gdb_json_printer/test/objects_37_foo.json
@@ -0,0 +1,47 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd768",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x55555556deb0",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x55555556deb0",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x55555556deb8",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x55555556ded0",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x55555556ded0",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/reference_41_bar.json b/gdb/gdb_json_printer/test/reference_41_bar.json
new file mode 100644
index 0000000..22d33f7
--- /dev/null
+++ b/gdb/gdb_json_printer/test/reference_41_bar.json
@@ -0,0 +1,17 @@
+{
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd74e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd74e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/reference_41_bar_ptr.json b/gdb/gdb_json_printer/test/reference_41_bar_ptr.json
new file mode 100644
index 0000000..3fd919d
--- /dev/null
+++ b/gdb/gdb_json_printer/test/reference_41_bar_ptr.json
@@ -0,0 +1,22 @@
+{
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd760",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd74e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd74e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/reference_41_bar_ref.json b/gdb/gdb_json_printer/test/reference_41_bar_ref.json
new file mode 100644
index 0000000..d5515f3
--- /dev/null
+++ b/gdb/gdb_json_printer/test/reference_41_bar_ref.json
@@ -0,0 +1,22 @@
+{
+ "type": "reference",
+ "ctype": "Bar &",
+ "address": "0x7fffffffd74e",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd74e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd74e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/reference_41_foo.json b/gdb/gdb_json_printer/test/reference_41_foo.json
new file mode 100644
index 0000000..9fec7d5
--- /dev/null
+++ b/gdb/gdb_json_printer/test/reference_41_foo.json
@@ -0,0 +1,42 @@
+{
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd750",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd750",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd758",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd74e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd74e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/reference_41_foo_ptr.json b/gdb/gdb_json_printer/test/reference_41_foo_ptr.json
new file mode 100644
index 0000000..9d965e8
--- /dev/null
+++ b/gdb/gdb_json_printer/test/reference_41_foo_ptr.json
@@ -0,0 +1,47 @@
+{
+ "type": "pointer",
+ "ctype": "Foo *",
+ "address": "0x7fffffffd768",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd750",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd750",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd758",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd74e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd74e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test/reference_41_foo_ref.json b/gdb/gdb_json_printer/test/reference_41_foo_ref.json
new file mode 100644
index 0000000..76975dc
--- /dev/null
+++ b/gdb/gdb_json_printer/test/reference_41_foo_ref.json
@@ -0,0 +1,47 @@
+{
+ "type": "reference",
+ "ctype": "Foo &",
+ "address": "0x7fffffffd750",
+ "reference": {
+ "type": "struct",
+ "ctype": "Foo",
+ "address": "0x7fffffffd750",
+ "fields": [
+ {
+ "field": "a",
+ "value": {
+ "type": "int",
+ "ctype": "uint8_t",
+ "address": "0x7fffffffd750",
+ "value": "1"
+ }
+ },
+ {
+ "field": "bar",
+ "value": {
+ "type": "pointer",
+ "ctype": "Bar *",
+ "address": "0x7fffffffd758",
+ "reference": {
+ "type": "struct",
+ "ctype": "Bar",
+ "address": "0x7fffffffd74e",
+ "fields": [
+ {
+ "field": "b",
+ "value": {
+ "type": "int",
+ "ctype": "uint16_t",
+ "address": "0x7fffffffd74e",
+ "value": "2"
+ }
+ }
+ ],
+ "base_classes": []
+ }
+ }
+ }
+ ],
+ "base_classes": []
+ }
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test_examples/array.cpp b/gdb/gdb_json_printer/test_examples/array.cpp
new file mode 100644
index 0000000..d2a9b85
--- /dev/null
+++ b/gdb/gdb_json_printer/test_examples/array.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+class Bar {
+public:
+ uint16_t b = 2;
+};
+
+class Foo {
+public:
+ uint8_t a = 1;
+ Bar *bar = nullptr;
+};
+
+int main() {
+ int16_t a[5] = {1, 2, 3, 4, 5};
+ int16_t* b = &a[2];
+ Foo foo[3];
+ Bar bar[3];
+ foo[0].bar = &bar[0];
+ foo[1].bar = &bar[1];
+ foo[2].bar = &bar[2];
+ Foo& foo1 = foo[0];
+
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test_examples/basic_types.cpp b/gdb/gdb_json_printer/test_examples/basic_types.cpp
new file mode 100644
index 0000000..dd13c02
--- /dev/null
+++ b/gdb/gdb_json_printer/test_examples/basic_types.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+
+enum Animal { CAT, DOG, TIGER };
+
+int main() {
+ {
+ uint8_t a = 1;
+ int8_t b = -2;
+ uint16_t c = 3;
+ int16_t d = -4;
+ uint32_t e = 5;
+ int32_t f = -6;
+ uint64_t g = 7;
+ int64_t h = -8;
+ float i = 9.0;
+ double j = -10.0;
+ const char *k = "Hello";
+ void *l = nullptr;
+ char *m = nullptr;
+ bool n = rand() % 2 == 0;
+ Animal o = CAT;
+ if (n) {
+ a += b;
+ }
+ std::cout << a + b + c + d + e + f + g + h + i + j << std::endl;
+ }
+ {
+ {
+ const uint8_t a = 1;
+ const int8_t b = -2;
+ const uint16_t c = 3;
+ const int16_t d = -4;
+ const uint32_t e = 5;
+ const int32_t f = -6;
+ const uint64_t g = 7;
+ const int64_t h = -8;
+ const float i = 9.0;
+ const double j = -10.0;
+ const char *k = "Hello";
+ const void *l = nullptr;
+ const char *m = nullptr;
+ const bool n = rand() % 2 == 0;
+ const Animal o = DOG;
+ if (n) {
+ std::cout << a + b + c + d + e + f + g + h + i + j << std::endl;
+ }
+ }
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test_examples/objects.cpp b/gdb/gdb_json_printer/test_examples/objects.cpp
new file mode 100644
index 0000000..9f3bb92
--- /dev/null
+++ b/gdb/gdb_json_printer/test_examples/objects.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+class Bar {
+public:
+ uint16_t b = 2;
+};
+
+class Foo {
+public:
+ uint8_t a = 1;
+ Bar *bar = nullptr;
+};
+
+int main() {
+ {
+ Foo *foo = new Foo();
+ Bar *bar = new Bar();
+ foo->bar = bar;
+ delete foo;
+ delete bar;
+ }
+ {
+ std::unique_ptr<Foo> foo = std::make_unique<Foo>();
+ std::unique_ptr<Bar> bar = std::make_unique<Bar>();
+ foo->bar = bar.get();
+ foo->bar->b++;
+ }
+ {
+ std::shared_ptr<Foo> foo = std::make_shared<Foo>();
+ std::shared_ptr<Bar> bar = std::make_shared<Bar>();
+ foo->bar = bar.get();
+ foo->bar->b++;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test_examples/objects1.cpp b/gdb/gdb_json_printer/test_examples/objects1.cpp
new file mode 100644
index 0000000..dc4e4d4
--- /dev/null
+++ b/gdb/gdb_json_printer/test_examples/objects1.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+class Foo;
+
+class Bar {
+public:
+ uint16_t b = 2;
+ Foo *foo = nullptr;
+};
+
+class Foo {
+public:
+ uint8_t a = 1;
+ Bar *bar = nullptr;
+};
+
+int main() {
+ {
+ Foo *foo = new Foo();
+ Bar *bar = new Bar();
+ foo->bar = bar;
+ bar->foo = foo;
+ delete foo;
+ delete bar;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test_examples/objects2.cpp b/gdb/gdb_json_printer/test_examples/objects2.cpp
new file mode 100644
index 0000000..7985cff
--- /dev/null
+++ b/gdb/gdb_json_printer/test_examples/objects2.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+class Foo {
+public:
+ uint8_t a = 1;
+ virtual int get() = 0;
+};
+
+class Foo1 : public Foo {
+ uint16_t b = 2;
+ virtual int get() override { return b; }
+};
+
+class Foo2 : public Foo {
+public:
+ uint32_t c = 3;
+ uint32_t cc = 4;
+ virtual int get() override { return c; }
+};
+
+class Foo3 : public Foo1, public Foo2 {
+ uint32_t d = 4;
+ virtual int get() override { return d; }
+};
+
+class Foo4 : virtual public Foo {
+ uint16_t b = 2;
+ virtual int get() override { return b; }
+};
+
+class Foo5 : virtual public Foo {
+public:
+ uint32_t c = 3;
+ uint32_t cc = 4;
+ virtual int get() override { return c; }
+};
+
+class Foo6 : public Foo4, public Foo5 {
+ uint32_t d = 4;
+ virtual int get() override { return d; }
+};
+
+class Bar {
+public:
+ uint8_t e = 5;
+};
+
+class Bar1 : virtual public Bar {
+public:
+ uint8_t f = 6;
+};
+
+int main() {
+ {
+ Foo *x = new Foo1();
+ Foo *y = new Foo2();
+ Foo1 *z = new Foo3();
+ Foo *w = z;
+ Bar *a = new Bar1();
+ Foo2 b;
+ Foo &c = *w;
+ Foo1 d = *z;
+ b.c += 1;
+ Foo *e = new Foo6();
+ Foo4 *f = dynamic_cast<Foo6 *>(e);
+
+ delete a;
+ delete x;
+ delete y;
+ delete z;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/gdb_json_printer/test_examples/reference.cpp b/gdb/gdb_json_printer/test_examples/reference.cpp
new file mode 100644
index 0000000..e86b7b4
--- /dev/null
+++ b/gdb/gdb_json_printer/test_examples/reference.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+class Bar {
+public:
+ uint16_t b = 2;
+};
+
+class Foo {
+public:
+ uint8_t a = 1;
+ Bar *bar = nullptr;
+};
+
+int main() {
+ Foo foo;
+ Bar bar;
+ foo.bar = &bar;
+ Foo &foo_ref = foo;
+ Bar &bar_ref = bar;
+ Foo *foo_ptr = &foo;
+ Bar *bar_ptr = &bar;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/heap_print/README.md b/gdb/heap_print/README.md
new file mode 100644
index 0000000..63a767c
--- /dev/null
+++ b/gdb/heap_print/README.md
@@ -0,0 +1,44 @@
+Script supports 2 custom commands:
+
+1) watch_heap : sets break point at dynamic memory allocation and keeps track of it
+2) print_ptr : prints the memory pointed by raw pointer in hex format.
+
+ eg:
+ (gdb) print_ptr malloc_ptr
+ Type : int *
+ Starting Address: 0x55555556aeb0
+ Length : 40
+ 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x03 0x00 0x00 0x00
+ 0x04 0x00 0x00 0x00 0x05 0x00 0x00 0x00 0x06 0x00 0x00 0x00
+ 0x07 0x00 0x00 0x00 0x08 0x00 0x00 0x00 0x09 0x00 0x00 0x00
+ 0x0a 0x00 0x00 0x00
+
+ If print_ptr is used after free/delete[], then it would print "No address mapping found!"
+
+
+Tests:
+
+To run the test
+
+In the heap_print dir,
+
+Compile :
+
+g++ -O0 -g test/sample_heap_test.cc -o test/sample_heap_test.o
+
+And then Run:
+
+$ gdb
+$ source test/test_heap_print_script.py
+
+
+Future Goals:
+
+To handle pointer offset, for eg, (gdb) print_ptr malloc_ptr + 3
+
+To handle custom allacator, may be watch_heap command could take in arguements
+and sets additional break points.
+
+
+
+
diff --git a/gdb/heap_print/heap_print_script.py b/gdb/heap_print/heap_print_script.py
new file mode 100644
index 0000000..f65d56b
--- /dev/null
+++ b/gdb/heap_print/heap_print_script.py
@@ -0,0 +1,239 @@
+import gdb
+
+
+def parse_address_to_int(address):
+ int_address_string = gdb.execute(
+ 'p/d {}'.format(address), to_string=True)
+ int_address = int(int_address_string.split('=')[1].strip())
+ return int_address
+
+
+def parse_gdb_equals(str):
+ """
+ str is $1 = value. so it returns value
+ """
+ return str.split("=")[1].strip()
+
+
+class HeapMapping:
+ """
+ Wrapper class for dictionary to have customization for the dictionary
+ and one entry point
+ """
+
+ address_length_mapping = {}
+ address_set = set()
+
+ @staticmethod
+ def put(address, length):
+ HeapMapping.address_length_mapping[address] = length
+ HeapMapping.address_set.add(address)
+
+ @staticmethod
+ def get(address):
+ """
+ Gets the length of the dynamic array corresponding to address. Suppose dynamic
+ array is {1,2,3,4,5} and starting address is 400 which is passed as address to this
+ method, then method would return 20(i.e. 5 * sizeof(int)). When this address
+ is offsetted for eg 408 is passed to this method, then it will return remainder
+ number of bytes allocated, here it would be 12 (i.e. 420 - 408)
+ Algorithm tries to find address in address_length_apping, if it doesn't find it
+ then it tries to find the range that can fit the address. if it fails to find such
+ mapping then it would return None.
+ """
+
+ length_found = HeapMapping.address_length_mapping.get(address)
+ if length_found:
+ return length_found
+ else:
+ address_list = list(HeapMapping.address_set)
+ address_list.sort()
+ left = 0
+ right = len(address_list) - 1
+ while left <= right:
+ mid = int((left + right) / 2)
+ if address > address_list[mid]:
+ left = mid + 1
+ # only < case would be accounted in else.
+ # As == would be handled in the if-check above (outside while)
+ else:
+ right = mid - 1
+
+ index = left - 1
+ if index == -1:
+ return None
+ base_address = address_list[index]
+ base_len = HeapMapping.address_length_mapping.get(base_address)
+ if base_address + base_len > address:
+ return base_address + base_len - address
+ else:
+ return None
+
+ @staticmethod
+ def remove(address):
+ HeapMapping.address_length_mapping.pop(address, None)
+ HeapMapping.address_set.discard(address)
+
+
+class AllocationFinishedBreakpoint(gdb.FinishBreakpoint):
+ """
+ Sets temporary breakpoints on returns (specifically returns of memory allocations)
+ to record address allocated.
+ It get instantiated from AllocationBreakpoint and ReallocationBreakpoint. When it is
+ instantiated from ReallocationBreakPoint, it carries prev_address.
+ """
+
+ def __init__(self, length, prev_address=None):
+ super().__init__(internal=True)
+ self.length = length
+ self.prev_address = prev_address
+
+ def stop(self):
+ """
+ Called when the return address in the current frame is hit. It parses hex address
+ into int address. If return address is not null then it stores address and length
+ into the address_length_mapping dictionary.
+ """
+
+ return_address = self.return_value
+ if return_address is not None or return_address == 0x0:
+ if self.prev_address != None:
+ HeapMapping.remove(self.prev_address)
+
+ # Converting hex address to int address
+ int_address = parse_address_to_int(return_address)
+ HeapMapping.put(int_address, self.length)
+ return False
+
+
+class AllocationBreakpoint(gdb.Breakpoint):
+ """
+ Handler class when malloc and operator new[] gets hit
+ """
+
+ def __init__(self, spec):
+ super().__init__(spec, internal=True)
+
+ def stop(self):
+ # handle malloc and new
+ func_args_string = gdb.execute('info args', to_string=True)
+ if func_args_string.find("=") != -1:
+ # There will be just 1 argument to malloc. So no need to handle multiline
+ length = int(parse_gdb_equals(func_args_string))
+ AllocationFinishedBreakpoint(length)
+ return False
+
+
+class ReallocationBreakpoint(gdb.Breakpoint):
+ """
+ Handler class when realloc gets hit
+ """
+
+ def __init__(self, spec):
+ super().__init__(spec, internal=True)
+
+ def stop(self):
+ # handle realloc
+ func_args_string = gdb.execute('info args', to_string=True)
+ if func_args_string.find("=") != -1:
+ args = func_args_string.split("\n")
+ address = parse_gdb_equals(args[0])
+ int_address = parse_address_to_int(address)
+ length = int(parse_gdb_equals(args[1]))
+ AllocationFinishedBreakpoint(length, int_address)
+ return False
+
+
+class DeallocationBreakpoint(gdb.Breakpoint):
+ """
+ Handler class when free and operator delete[] gets hit
+ """
+
+ def __init__(self, spec):
+ super().__init__(spec, internal=True)
+
+ def stop(self):
+ func_args_string = gdb.execute('info args', to_string=True)
+ if func_args_string.find("=") != -1:
+ address = parse_gdb_equals(func_args_string)
+ int_address = parse_address_to_int(address)
+ HeapMapping.remove(int_address)
+ return False
+
+
+class WatchHeap(gdb.Command):
+ """
+ Custom Command to keep track of Heap Memory Allocation.
+ Currently keeps tracks of memory allocated/deallocated using
+ malloc, realloc, free, operator new[] and operator delete[]
+ """
+
+ def __init__(self):
+ super(WatchHeap, self).__init__("watch_heap", gdb.COMMAND_USER)
+
+ def complete(self, text, word):
+ return gdb.COMPLETE_COMMAND
+
+ def invoke(self, args, from_tty):
+ # TODO : Check whether break location methods are defined
+ AllocationBreakpoint("malloc")
+ AllocationBreakpoint("operator new[]")
+ ReallocationBreakpoint("realloc")
+ DeallocationBreakpoint("free")
+ DeallocationBreakpoint("operator delete[]")
+
+
+class PrintHeapPointer(gdb.Command):
+ """
+ Custom command to print memory allocated at dynamic time
+ """
+
+ def __init__(self):
+ super(PrintHeapPointer, self).__init__("print_ptr", gdb.COMMAND_USER)
+
+ def complete(self, text, word):
+ return gdb.COMPLETE_COMMAND
+
+ def invoke(self, args, from_tty=True):
+ try:
+ value = gdb.parse_and_eval(args)
+ if value.type.code == gdb.TYPE_CODE_PTR:
+ print("Type : ", value.type)
+ starting_address_string = gdb.execute(
+ 'p/x {}'.format(value), to_string=True)
+ print("Address: ",
+ parse_gdb_equals(starting_address_string))
+ int_address = parse_address_to_int(value)
+ # print memory
+ self.print_heap(int_address)
+ except Exception:
+ print('No symbol found!')
+
+ def print_heap(self, address):
+ """
+ Prints the memory that is being pointed by address in hex format
+
+ Parameters
+ ---------
+ address : raw pointer
+ """
+
+ memory_size = HeapMapping.get(address)
+ if memory_size:
+ print('Length :', memory_size)
+ result = ''
+ i = 0
+ while i < memory_size:
+ byte_string = gdb.execute(
+ 'x/1bx {}'.format(address), to_string=True)
+ result += byte_string.split(':')[1].strip() + " "
+ address += 1
+ i += 1
+ print(result)
+ else:
+ print("No address mapping found!")
+
+
+if __name__ == '__main__':
+ WatchHeap()
+ PrintHeapPointer()
diff --git a/gdb/heap_print/test/sample_heap_test.cc b/gdb/heap_print/test/sample_heap_test.cc
new file mode 100644
index 0000000..783c471
--- /dev/null
+++ b/gdb/heap_print/test/sample_heap_test.cc
@@ -0,0 +1,45 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <new>
+
+int main()
+{
+ int n = 10;
+
+ // Dynamically allocate memory using malloc()
+ int *malloc_ptr = (int *)malloc(n * sizeof(int));
+
+ if (malloc_ptr != NULL)
+ {
+ //Just Feeding data
+ for (int i = 0; i < n; ++i)
+ {
+ malloc_ptr[i] = i + 1;
+ }
+ //For checking realloc
+ int new_n = 20;
+ malloc_ptr = (int *)realloc(malloc_ptr, new_n * sizeof(int));
+ for (int i = 0; i < new_n; ++i)
+ {
+ malloc_ptr[i] = i + 1;
+ }
+ //For checking free
+ free(malloc_ptr);
+ }
+
+ // Dynamically allocating memory using operator new[]
+ int *new_ptr = new int[n];
+ if (new_ptr != NULL)
+ {
+ //Just feeding data
+ for (int i = 0; i < n; ++i)
+ {
+ new_ptr[i] = i + 1;
+ }
+
+ //For checking operator delete[]
+ delete[] new_ptr;
+ }
+ printf("Done");
+ return 0;
+}
\ No newline at end of file
diff --git a/gdb/heap_print/test/test_heap_print_script.py b/gdb/heap_print/test/test_heap_print_script.py
new file mode 100644
index 0000000..5518527
--- /dev/null
+++ b/gdb/heap_print/test/test_heap_print_script.py
@@ -0,0 +1,113 @@
+import unittest
+import gdb
+
+
+def get_n(n_str):
+ return int(n_str.split("=")[1].strip())
+
+
+class HeapMemoryTest(unittest.TestCase):
+
+ def setUp(self):
+ gdb.execute('set pagination on')
+ gdb.execute("file test/sample_heap_test.o")
+ gdb.execute("source heap_print_script.py")
+ gdb.execute("delete")
+ gdb.execute("watch_heap")
+
+ def check_memory(self, n, array_ptr_str, offset=1):
+ """
+ It is used to test what we got from 'print_ptr' is what we expect.
+ Sample test program allocates array of n int's using malloc and then
+ assigns 1 to n values to that array. So checking that malloc_ptr_str
+ is 1 to n, following big endian size and size of int as 32 bits
+
+ Parameters
+ ----------
+ n : int
+ array length
+ array_ptr_str : str
+ whole output from print_ptr command including memory content
+ offset : int
+ checking memory content starts from offset value. By default it is 1
+ """
+
+ data = array_ptr_str.split("\n")[3]
+ bytes_from_heap = data.split(" ")
+ actual_start = offset
+ for i in range(0, n * 4, 4):
+ hex_str = bytes_from_heap[i+3][2:]
+ hex_str += bytes_from_heap[i+2][2:]
+ hex_str += bytes_from_heap[i+1][2:]
+ hex_str += bytes_from_heap[i][2:]
+ int_of_hex = int(hex_str, 16)
+ self.assertEqual(actual_start, int_of_hex)
+ actual_start += 1
+
+ def test_malloc(self):
+ print("malloc test")
+ gdb.execute("b 20")
+ gdb.execute("r")
+ n_str = gdb.execute("print n", to_string=True)
+ n = get_n(n_str)
+ malloc_ptr_array_str = gdb.execute(
+ "print_ptr malloc_ptr", to_string=True)
+ print(malloc_ptr_array_str)
+ self.check_memory(n, malloc_ptr_array_str)
+ self.assertTrue(True)
+
+ def test_realloc(self):
+ print("realloc test")
+ gdb.execute("b 27")
+ gdb.execute("r")
+ new_n = gdb.execute("print new_n", to_string=True)
+ n = get_n(new_n)
+ malloc_ptr_str = gdb.execute("print_ptr malloc_ptr", to_string=True)
+ print(malloc_ptr_str)
+ self.check_memory(n, malloc_ptr_str)
+
+ def test_offset(self):
+ """
+ Testcase to test raw_pointers that are offset
+ """
+
+ print("offset test. we have array of 20 (80 bytes) and \
+ we offset it by 3, so new length should be 68")
+ offset = 3
+ gdb.execute("b 27")
+ gdb.execute("r")
+ new_n = gdb.execute("print new_n", to_string=True)
+ n = get_n(new_n)
+ malloc_ptr_str = gdb.execute(
+ "print_ptr malloc_ptr + {}".format(offset), to_string=True)
+ print(malloc_ptr_str)
+ self.check_memory(n - offset, malloc_ptr_str, offset+1)
+
+ def test_free(self):
+ print("free test")
+ gdb.execute("b 28")
+ gdb.execute("r")
+ malloc_ptr_str = gdb.execute("print_ptr malloc_ptr", to_string=True)
+ data = malloc_ptr_str.split("\n")[2].strip()
+ self.assertEqual(data, "No address mapping found!")
+
+ def test_new(self):
+ print("operator new[] test")
+ gdb.execute("b 41")
+ gdb.execute("r")
+ n_str = gdb.execute("print n", to_string=True)
+ n = get_n(n_str)
+ new_ptr_array_str = gdb.execute("print_ptr new_ptr", to_string=True)
+ self.check_memory(n, new_ptr_array_str)
+
+ def test_delete(self):
+ print("operator delete[]")
+ gdb.execute("b 42")
+ gdb.execute("r")
+ new_ptr_array_str = gdb.execute("print_ptr new_ptr", to_string=True)
+ data = new_ptr_array_str.split("\n")[2].strip()
+ self.assertEqual(data, "No address mapping found!")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/gdb/parameters_extract.py b/gdb/parameters_extract.py
new file mode 100644
index 0000000..b1af70c
--- /dev/null
+++ b/gdb/parameters_extract.py
@@ -0,0 +1,288 @@
+import os
+import re
+
+# If doing it on device with gdbserver
+DEVICE = os.environ.get('GDBSCRIPT_ON_DEVICE', False)
+# Path of the file on device
+DEVICE_FILEPATH = os.environ.get('GDBSCRIPT_FILENAME', None)
+# GDBServer's port
+DEVICE_PORT = os.environ.get('GDBSCRIPT_DEVICE_PORT', 4444)
+# Serial number of device for adb
+DEVICE_SERIAL = os.environ.get('GDBSCRIPT_DEVICE_SERIAL', None)
+
+def check_device_args():
+ """
+ Checks if FILEPATH is provided if the execution is on device
+ """
+ if not DEVICE:
+ return
+
+ if not DEVICE_FILEPATH:
+ raise ValueError("Filename (GDBSCRIPT_FILEPATH) not provided")
+
+class RecordPoint(gdb.Breakpoint):
+ """
+ A custom breakpoint that records the arguments when the breakpoint is hit and continues
+ Also enables the next breakpoint and disables all the ones after it
+ """
+ def stop(self):
+ """
+ The function that's called when a breakpoint is hit. If we return true,
+ it halts otherwise it continues
+ We always return false because we just need to record the value and we
+ can do it without halting the program
+ """
+ self.args[self.times_hit % self.count] = get_function_args()
+ self.times_hit += 1
+
+ if self.next_bp != None:
+ self.next_bp.previous_hit()
+
+ return False
+
+ def previous_hit(self):
+ """
+ This function is called if the previous breakpoint is hit so it can enable
+ itself and disable the next ones
+ """
+ self.enabled = True
+
+ if self.next_bp != None:
+ self.next_bp.propagate_disable()
+
+ def propagate_disable(self):
+ """
+ Disabled all the breakpoints after itself
+ """
+ self.enabled = False
+ if self.next_bp != None:
+ self.next_bp.propagate_disable()
+
+ def process_arguments(self):
+ """
+ Orders the recorded arguments into the right order (oldest to newest)
+ """
+ current_hit_point = self.times_hit % self.count
+ # Split at the point of current_hit_point because all the entries after it
+ # are older than the ones before it
+ self.processed_args = self.args[current_hit_point:] + self.args[:current_hit_point]
+ self.current_arg_idx = 0
+
+ def get_arguments(self):
+ """
+ Gets the current argument value.
+ Should be called the same amount of times as the function was called
+ in the stacktrace
+ First call returns the arguments recorded for the first call in the stacktrace
+ and so on.
+ """
+ if self.current_arg_idx >= len(self.processed_args):
+ raise ValueError("Cannot get arguments more times than the function \
+ was present in stacktrace")
+
+ cur = self.processed_args[self.current_arg_idx]
+ self.current_arg_idx += 1
+ return cur
+
+def init_gdb():
+ """
+ Initialized the GDB specific stuff
+ """
+ gdb.execute('set pagination off')
+ gdb.execute('set print frame-arguments all')
+ if DEVICE:
+ gdb.execute('target extended-remote :{}'.format(DEVICE_PORT))
+ gdb.execute('set remote exec-file /data/local/tmp/{}'.format(DEVICE_FILEPATH))
+
+def initial_run():
+ """
+ The initial run of the program which captures the stacktrace in init.log file
+ """
+ gdb.execute('r > init.log 2>&1',from_tty=True, to_string=True)
+ if DEVICE:
+ if DEVICE_SERIAL:
+ os.system('adb -s "{}" pull /data/local/tmp/init.log'.format(DEVICE_SERIAL))
+ else:
+ os.system("adb pull /data/local/tmp/init.log")
+ with open("init.log", "rb") as f:
+ out = f.read().decode()
+ return out
+
+def gdb_exit():
+ """
+ Exits the GDB instance
+ """
+ gdb.execute('q')
+
+def get_stacktrace_functions(stacktrace):
+ """
+ Gets the functions from ASAN/HWASAN's stacktrace
+ Args:
+ stacktrace: (string) ASAN/HWASAN's stacktrace output
+ Returns:
+ functions: (list) functions in the stacktrace
+ """
+ stacktrace_start = stacktrace[stacktrace.index('==ERROR: '):].split("\n")
+ functions = []
+
+ # skip the first two lines of stacktrace
+ for line in stacktrace_start[2:]:
+ if line == "":
+ break
+
+ # Extracts the function name from a line like this
+ # "#0 0xaddress in function_name() file/path.cc:xx:yy"
+ func_name = line.strip().split(" ")[3]
+ if '(' in func_name:
+ func_name = func_name[:func_name.index('(')]
+
+ functions.append(func_name)
+
+ #remove last function from stacktrace because it would be _start
+ return functions
+
+def parse_function_arguments(func_info):
+ """
+ Parses the output of 'whatis' command into a list of arguments
+ "void (teststruct)" --> ["teststruct"]
+ "int (int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **),
+ void (*)(void), void (*)(void), void *)" --> ['int (*)(int, char **, char **)',
+ 'int', 'char **', 'int (*)(int, char **, char **)', 'void (*)(void)',
+ 'void (*)(void)', ' void *']
+
+ Args:
+ func_info: (string) output of gdb's 'whatis' command for a function
+ Returns:
+ parsed_params: (list) parsed parameters of the function
+ """
+ if '(' not in func_info:
+ return []
+ func_params = func_info[func_info.index('(')+1:-1]
+ parentheses_count = 0
+ current_param = ""
+ parsed_params = []
+
+ for token in func_params:
+ # Essentially trying to get the data types from a function declaration
+ if token == '(':
+ parentheses_count += 1
+ elif token == ')':
+ parentheses_count -= 1
+
+ # If we are not inside any paren and see a ',' it signals the start of
+ #the next parameter
+ if token == ',' and parentheses_count == 0:
+ parsed_params.append(current_param.strip())
+ current_param = ""
+ else:
+ current_param += token
+
+ parsed_params.append(current_param)
+ return parsed_params
+
+def parse_stacktrace(stacktrace):
+ """
+ Parses the ASAN/HWASAN's stacktrace to a list of functions, their addresses
+ and argument types
+ Args:
+ stacktrace: (string) ASAN/HWASAN's stacktrace output
+ Returns:
+ functions_info: (list) parsed function information as a dictionary
+ """
+ stacktrace_functions = get_stacktrace_functions(stacktrace)[:-1]
+ functions_info = []
+ for function in stacktrace_functions:
+ # Gets the value right hand side of gdb's whatis command.
+ # "type = {function info}" -> "{function info}"
+ func_info = gdb.execute('whatis {}'.format(function),
+ to_string=True).split(' = ')[1].strip()
+ # Uses gdb's x/i to print its address and parse it from hex to int
+ address = int(gdb.execute("x/i {}".format(function),
+ to_string=True).strip().split(" ")[0], 16)
+ functions_info.append({'name': function, 'address':address,
+ 'arguments' : parse_function_arguments(func_info)})
+ #In the order they are called in the execution
+ return functions_info[::-1]
+
+def get_function_args():
+ """
+ Gets the current function arguments
+ """
+ args = gdb.execute('info args -q', to_string=True).strip()
+ return args
+
+def functions_to_breakpoint(parsed_functions):
+ """
+ Sets the breakpoint at every function and returns a dictionary mapping the
+ function to it's breakpoint
+ Args:
+ parsed_functions: (list) functions in the stacktrace (in the same order) as
+ dictionary with "name" referring to the function name
+ ({"name" : function_name})
+ Returns:
+ function_breakpoints: (dictionary) maps the function name to its
+ breakpoint object
+ """
+ function_breakpoints = {}
+ last_bp = None
+
+ for function in reversed(parsed_functions):
+ function_name = function['name']
+ if function_name in function_breakpoints:
+ function_breakpoints[function_name].count += 1
+ function_breakpoints[function_name].args.append(None)
+ continue
+
+ cur_bp = RecordPoint("{}".format(function_name))
+ cur_bp.count = 1
+ cur_bp.times_hit = 0
+ cur_bp.args = []
+ cur_bp.args.append(None)
+ cur_bp.next_bp = last_bp
+
+ function_breakpoints[function['name']] = cur_bp
+ last_bp = cur_bp
+
+ return function_breakpoints
+
+def run(parsed_functions):
+ """
+ Runs the whole thing by setting up breakpoints and printing them after
+ excecution is done
+ Args:
+ parsed_functions: A list of functions in the stacktrace (in the same order)
+ as dictionary with "name" referring to the function name
+ ({"name" : function_name})
+ """
+ names = [function['name'] for function in parsed_functions]
+ breakpoints = functions_to_breakpoint(parsed_functions)
+
+ #Disable all breakpoints at start
+ for bp in breakpoints:
+ breakpoints[bp].enabled = False
+
+ breakpoints[names[0]].enabled = True
+
+ gdb.execute('r')
+ for breakpoint in breakpoints:
+ breakpoints[breakpoint].process_arguments()
+
+ function_args = []
+ for name in names:
+ print("-----------")
+ print("Function -> {}".format(name))
+
+ function_args.append({'function':name,
+ 'arguments' : breakpoints[name].get_arguments()})
+ print(function_args[-1]['arguments'])
+
+ return function_args
+
+
+if __name__ == '__main__':
+ check_device_args()
+ init_gdb()
+ initial_out = initial_run()
+ function_data = parse_stacktrace(initial_out)
+ run(function_data)
+ gdb_exit()
diff --git a/remote_provisioning/OWNERS b/remote_provisioning/OWNERS
new file mode 100644
index 0000000..78a6711
--- /dev/null
+++ b/remote_provisioning/OWNERS
@@ -0,0 +1,3 @@
+jbires@google.com
+swillden@google.com
+wedsonaf@google.com
diff --git a/remote_provisioning/attestation_testing/Android.bp b/remote_provisioning/attestation_testing/Android.bp
new file mode 100644
index 0000000..ec7bae9
--- /dev/null
+++ b/remote_provisioning/attestation_testing/Android.bp
@@ -0,0 +1,12 @@
+android_app {
+ name: "AttestationTestTool",
+ srcs: [
+ "java/**/*.java",
+ ],
+ manifest: "AndroidManifest.xml",
+ resource_dirs: ["res"],
+ static_libs: ["androidx.appcompat_appcompat",
+ "bouncycastle-unbundled",
+ "guava"],
+ platform_apis: true,
+}
diff --git a/remote_provisioning/attestation_testing/AndroidManifest.xml b/remote_provisioning/attestation_testing/AndroidManifest.xml
new file mode 100644
index 0000000..9af01e5
--- /dev/null
+++ b/remote_provisioning/attestation_testing/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.attestationexample">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher">
+ <activity
+ android:name=".AttestationActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/remote_provisioning/attestation_testing/README b/remote_provisioning/attestation_testing/README
new file mode 100644
index 0000000..fc92654
--- /dev/null
+++ b/remote_provisioning/attestation_testing/README
@@ -0,0 +1,8 @@
+Run the following commands to run this attestation test on device. From the root of the tree:
+> . build/envsetup.sh
+> lunch <<your_device_target>>
+> m AttestationTestTool
+> python tools/security/remote_provisioning/attestation_testing/attestation_test_host.py
+
+Any failures found during execution will be printed to the terminal in the format of:
+> AttestationFail: <<Description of failure>>
diff --git a/remote_provisioning/attestation_testing/attestation_test_host.py b/remote_provisioning/attestation_testing/attestation_test_host.py
new file mode 100644
index 0000000..417842a
--- /dev/null
+++ b/remote_provisioning/attestation_testing/attestation_test_host.py
@@ -0,0 +1,38 @@
+from sys import argv
+import os
+import subprocess
+import time
+
+APK_DIR = '${ANDROID_PRODUCT_OUT}/system/app/AttestationTestTool/AttestationTestTool.apk'
+FAILURE_TAG = 'AttestationFail'
+FAILURE_PREFIX = 'Failure: '
+FINISHED_TAG = 'AttestationFinished'
+INFO_TAG = 'AttestationFailInfo'
+INFO_PREFIX = ' ' * len(FAILURE_PREFIX)
+devnull = open(os.devnull, 'wb')
+
+# Clear logcat
+subprocess.call('adb logcat -c', shell=True, stdout=devnull)
+subprocess.call('adb install -r ' + APK_DIR, shell=True, stdout=devnull)
+subprocess.call('adb shell am start -a android.intent.action.MAIN -n com.google.attestationexample/.AttestationActivity',
+ shell=True, stdout=devnull)
+finished = False
+read_retry = 0
+failures = 0
+while not finished and read_retry < 5:
+ time.sleep(1)
+ logcat = subprocess.check_output(['adb', 'logcat', '-d'], stderr=subprocess.STDOUT)
+ for line in logcat.split('\n'):
+ if INFO_TAG in line:
+ print INFO_PREFIX + line[line.index('AttestationFailInfo') + len('AttestationFailInfo:'):]
+ elif FAILURE_TAG in line:
+ failures += 1
+ print FAILURE_PREFIX + line[line.index('AttestationFail') + len('AttestationFail:'):]
+ elif FINISHED_TAG in line and not finished:
+ print 'Finished. Failures: ' + str(failures)
+ finished = True
+ break
+ read_retry += 1
+ if read_retry is 5:
+ print 'Attestation test did not complete, check logcat to determine the source of the error'
+subprocess.call('adb uninstall com.google.attestationexample', shell=True, stdout=devnull)
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/Asn1Utils.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/Asn1Utils.java
new file mode 100644
index 0000000..c3dc62b
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/Asn1Utils.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.attestationexample;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DEROctetString;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.String;
+import java.math.BigInteger;
+import java.security.cert.CertificateParsingException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Set;
+
+public class Asn1Utils {
+
+ public static int getIntegerFromAsn1(ASN1Encodable asn1Value)
+ throws CertificateParsingException {
+ if (asn1Value instanceof ASN1Integer) {
+ return bigIntegerToInt(((ASN1Integer) asn1Value).getValue());
+ } else if (asn1Value instanceof ASN1Enumerated) {
+ return bigIntegerToInt(((ASN1Enumerated) asn1Value).getValue());
+ } else {
+ throw new CertificateParsingException(
+ "Integer value expected, " + asn1Value.getClass().getName() + " found.");
+ }
+ }
+
+ public static Long getLongFromAsn1(ASN1Encodable asn1Value) throws CertificateParsingException {
+ if (asn1Value instanceof ASN1Integer) {
+ return bigIntegerToLong(((ASN1Integer) asn1Value).getValue());
+ } else {
+ throw new CertificateParsingException(
+ "Integer value expected, " + asn1Value.getClass().getName() + " found.");
+ }
+ }
+
+ public static byte[] getByteArrayFromAsn1(ASN1Encodable asn1Encodable)
+ throws CertificateParsingException {
+ if (asn1Encodable == null || !(asn1Encodable instanceof DEROctetString)) {
+ throw new CertificateParsingException("Expected DEROctetString");
+ }
+ ASN1OctetString derOctectString = (ASN1OctetString) asn1Encodable;
+ return derOctectString.getOctets();
+ }
+
+ public static ASN1Encodable getAsn1EncodableFromBytes(byte[] bytes)
+ throws CertificateParsingException {
+ try (ASN1InputStream asn1InputStream = new ASN1InputStream(bytes)) {
+ return asn1InputStream.readObject();
+ } catch (IOException e) {
+ throw new CertificateParsingException("Failed to parse Encodable", e);
+ }
+ }
+
+ public static ASN1Sequence getAsn1SequenceFromBytes(byte[] bytes)
+ throws CertificateParsingException {
+ try (ASN1InputStream asn1InputStream = new ASN1InputStream(bytes)) {
+ return getAsn1SequenceFromStream(asn1InputStream);
+ } catch (IOException e) {
+ throw new CertificateParsingException("Failed to parse SEQUENCE", e);
+ }
+ }
+
+ public static ASN1Sequence getAsn1SequenceFromStream(final ASN1InputStream asn1InputStream)
+ throws IOException, CertificateParsingException {
+ ASN1Primitive asn1Primitive = asn1InputStream.readObject();
+ if (!(asn1Primitive instanceof ASN1OctetString)) {
+ throw new CertificateParsingException(
+ "Expected octet stream, found " + asn1Primitive.getClass().getName());
+ }
+ try (ASN1InputStream seqInputStream = new ASN1InputStream(
+ ((ASN1OctetString) asn1Primitive).getOctets())) {
+ asn1Primitive = seqInputStream.readObject();
+ if (!(asn1Primitive instanceof ASN1Sequence)) {
+ throw new CertificateParsingException(
+ "Expected sequence, found " + asn1Primitive.getClass().getName());
+ }
+ return (ASN1Sequence) asn1Primitive;
+ }
+ }
+
+ public static Set<Integer> getIntegersFromAsn1Set(ASN1Encodable set)
+ throws CertificateParsingException {
+ if (!(set instanceof ASN1Set)) {
+ throw new CertificateParsingException(
+ "Expected set, found " + set.getClass().getName());
+ }
+
+ ImmutableSet.Builder<Integer> builder = ImmutableSet.builder();
+ for (Enumeration<?> e = ((ASN1Set) set).getObjects(); e.hasMoreElements();) {
+ builder.add(getIntegerFromAsn1((ASN1Integer) e.nextElement()));
+ }
+ return builder.build();
+ }
+
+ public static String getStringFromAsn1OctetStreamAssumingUTF8(ASN1Encodable encodable)
+ throws CertificateParsingException, UnsupportedEncodingException {
+ if (!(encodable instanceof ASN1OctetString)) {
+ throw new CertificateParsingException(
+ "Expected octet string, found " + encodable.getClass().getName());
+ }
+
+ ASN1OctetString octetString = (ASN1OctetString) encodable;
+ return new String(octetString.getOctets(), "UTF-8");
+ }
+
+ public static Date getDateFromAsn1(ASN1Primitive value) throws CertificateParsingException {
+ return new Date(getLongFromAsn1(value));
+ }
+
+ public static boolean getBooleanFromAsn1(ASN1Encodable value)
+ throws CertificateParsingException {
+ if (!(value instanceof ASN1Boolean)) {
+ throw new CertificateParsingException(
+ "Expected boolean, found " + value.getClass().getName());
+ }
+ return ((ASN1Boolean) value).isTrue();
+ }
+
+ private static int bigIntegerToInt(BigInteger bigInt) throws CertificateParsingException {
+ if (bigInt.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0
+ || bigInt.compareTo(BigInteger.ZERO) < 0) {
+ throw new CertificateParsingException("INTEGER out of bounds");
+ }
+ return bigInt.intValue();
+ }
+
+ private static long bigIntegerToLong(BigInteger bigInt) throws CertificateParsingException {
+ if (bigInt.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0
+ || bigInt.compareTo(BigInteger.ZERO) < 0) {
+ throw new CertificateParsingException("INTEGER out of bounds");
+ }
+ return bigInt.longValue();
+ }
+}
\ No newline at end of file
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/Attestation.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/Attestation.java
new file mode 100644
index 0000000..c94ff3f
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/Attestation.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.attestationexample;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.io.BaseEncoding;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * Parses an attestation certificate and provides an easy-to-use interface for examining the
+ * contents.
+ */
+public class Attestation {
+ static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
+ static final int ATTESTATION_VERSION_INDEX = 0;
+ static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
+ static final int KEYMASTER_VERSION_INDEX = 2;
+ static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
+ static final int ATTESTATION_CHALLENGE_INDEX = 4;
+ static final int UNIQUE_ID_INDEX = 5;
+ static final int SW_ENFORCED_INDEX = 6;
+ static final int TEE_ENFORCED_INDEX = 7;
+
+ public static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
+ public static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
+ public static final int KM_SECURITY_LEVEL_STRONGBOX = 2;
+
+ private final boolean haveAttestation;
+ private final int attestationVersion;
+ private final int attestationSecurityLevel;
+ private final int keymasterVersion;
+ private final int keymasterSecurityLevel;
+ private final byte[] attestationChallenge;
+ private final byte[] uniqueId;
+ private final AuthorizationList softwareEnforced;
+ private final AuthorizationList teeEnforced;
+
+
+ /**
+ * Constructs an {@code Attestation} object from the provided {@link X509Certificate},
+ * extracting the attestation data from the attestation extension.
+ *
+ * @throws CertificateParsingException if the certificate does not contain a properly-formatted
+ * attestation extension.
+ */
+ public Attestation(X509Certificate x509Cert) throws CertificateParsingException {
+ ASN1Sequence seq = getAttestationSequence(x509Cert);
+ if (seq == null) {
+ haveAttestation = false;
+ attestationVersion = 0;
+ attestationSecurityLevel = 0;
+ keymasterVersion = 0;
+ keymasterSecurityLevel = 0;
+ attestationChallenge = null;
+ uniqueId = null;
+ softwareEnforced = null;
+ teeEnforced = null;
+ return;
+ }
+
+ haveAttestation = true;
+ attestationVersion =
+ Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX));
+ attestationSecurityLevel =
+ Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX));
+ keymasterVersion = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX));
+ keymasterSecurityLevel =
+ Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX));
+
+ attestationChallenge =
+ Asn1Utils.getByteArrayFromAsn1(
+ seq.getObjectAt(Attestation.ATTESTATION_CHALLENGE_INDEX));
+
+ uniqueId = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(Attestation.UNIQUE_ID_INDEX));
+
+ softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX));
+ teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX));
+ }
+
+ public static String securityLevelToString(int attestationSecurityLevel) {
+ switch (attestationSecurityLevel) {
+ case KM_SECURITY_LEVEL_SOFTWARE:
+ return "Software";
+ case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
+ return "TEE";
+ case KM_SECURITY_LEVEL_STRONGBOX:
+ return "StrongBox";
+ default:
+ return "Unknown";
+ }
+ }
+
+ public int getAttestationVersion() {
+ return attestationVersion;
+ }
+
+ public int getAttestationSecurityLevel() {
+ return attestationSecurityLevel;
+ }
+
+ public int getKeymasterVersion() {
+ return keymasterVersion;
+ }
+
+ public int getKeymasterSecurityLevel() {
+ return keymasterSecurityLevel;
+ }
+
+ public byte[] getAttestationChallenge() {
+ return attestationChallenge;
+ }
+
+ public byte[] getUniqueId() {
+ return uniqueId;
+ }
+
+ public AuthorizationList getSoftwareEnforced() {
+ return softwareEnforced;
+ }
+
+ public AuthorizationList getTeeEnforced() {
+ return teeEnforced;
+ }
+
+ @Override
+ public String toString() {
+ if (!haveAttestation) {
+ return "No attestation";
+ }
+
+ StringBuilder s = new StringBuilder();
+ s.append("Attestation version: " + attestationVersion);
+ s.append("\nAttestation security: " + securityLevelToString(attestationSecurityLevel));
+ s.append("\nKM version: " + keymasterVersion);
+ s.append("\nKM security: " + securityLevelToString(keymasterSecurityLevel));
+
+ s.append("\nChallenge");
+ String stringChallenge = new String(attestationChallenge);
+ if (CharMatcher.ascii().matchesAllOf(stringChallenge)) {
+ s.append(": [" + stringChallenge + "]");
+ } else {
+ s.append(" (base64): [" + BaseEncoding.base64().encode(attestationChallenge) + "]");
+ }
+ if (uniqueId != null) {
+ s.append("\nUnique ID (base64): [" + BaseEncoding.base64().encode(uniqueId) + "]");
+ }
+
+ s.append("\n\n-- SW enforced --");
+ s.append(softwareEnforced);
+ s.append("\n\n-- TEE enforced --");
+ s.append(teeEnforced);
+ s.append("\n");
+
+ return s.toString();
+ }
+
+ private ASN1Sequence getAttestationSequence(X509Certificate x509Cert)
+ throws CertificateParsingException {
+ byte[] attestationExtensionBytes = x509Cert.getExtensionValue(KEY_DESCRIPTION_OID);
+ if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
+ return null;
+ }
+ return Asn1Utils.getAsn1SequenceFromBytes(attestationExtensionBytes);
+ }
+
+}
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationActivity.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationActivity.java
new file mode 100644
index 0000000..0fb8cf4
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationActivity.java
@@ -0,0 +1,21 @@
+package com.google.attestationexample;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class AttestationActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ runTests();
+ }
+
+ private void runTests() {
+ try {
+ new AttestationTest().execute();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationApplicationId.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationApplicationId.java
new file mode 100644
index 0000000..b0bd257
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationApplicationId.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.attestationexample;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+
+import java.security.cert.CertificateParsingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+
+public class AttestationApplicationId implements java.lang.Comparable<AttestationApplicationId> {
+ private static final int PACKAGE_INFOS_INDEX = 0;
+ private static final int SIGNATURE_DIGESTS_INDEX = 1;
+
+ private final List<AttestationPackageInfo> packageInfos;
+ private final List<byte[]> signatureDigests;
+
+ public AttestationApplicationId(Context context)
+ throws NoSuchAlgorithmException, NameNotFoundException {
+ PackageManager pm = context.getPackageManager();
+ int uid = context.getApplicationInfo().uid;
+ String[] packageNames = pm.getPackagesForUid(uid);
+ if (packageNames == null || packageNames.length == 0) {
+ throw new NameNotFoundException("No names found for uid");
+ }
+ packageInfos = new ArrayList<AttestationPackageInfo>();
+ for (String packageName : packageNames) {
+ // get the package info for the given package name including
+ // the signatures
+ PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
+ packageInfos.add(new AttestationPackageInfo(packageName, packageInfo.versionCode));
+ }
+ // The infos must be sorted, the implementation of Comparable relies on it.
+ packageInfos.sort(null);
+
+ // compute the sha256 digests of the signature blobs
+ signatureDigests = new ArrayList<byte[]>();
+ PackageInfo packageInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
+ for (Signature signature : packageInfo.signatures) {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ signatureDigests.add(sha256.digest(signature.toByteArray()));
+ }
+ // The digests must be sorted. the implementation of Comparable relies on it
+ signatureDigests.sort(new ByteArrayComparator());
+ }
+
+ public AttestationApplicationId(ASN1Encodable asn1Encodable)
+ throws CertificateParsingException {
+ if (!(asn1Encodable instanceof ASN1Sequence)) {
+ throw new CertificateParsingException(
+ "Expected sequence for AttestationApplicationId, found "
+ + asn1Encodable.getClass().getName());
+ }
+
+ ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
+ packageInfos = parseAttestationPackageInfos(sequence.getObjectAt(PACKAGE_INFOS_INDEX));
+ // The infos must be sorted, the implementation of Comparable relies on it.
+ packageInfos.sort(null);
+ signatureDigests = parseSignatures(sequence.getObjectAt(SIGNATURE_DIGESTS_INDEX));
+ // The digests must be sorted. the implementation of Comparable relies on it
+ signatureDigests.sort(new ByteArrayComparator());
+ }
+
+ public List<AttestationPackageInfo> getAttestationPackageInfos() {
+ return packageInfos;
+ }
+
+ public List<byte[]> getSignatureDigests() {
+ return signatureDigests;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int noOfInfos = packageInfos.size();
+ int i = 1;
+ for (AttestationPackageInfo info : packageInfos) {
+ sb.append("\n### Package info " + i + "/" + noOfInfos + " ###\n");
+ sb.append(info);
+ }
+ i = 1;
+ int noOfSigs = signatureDigests.size();
+ for (byte[] sig : signatureDigests) {
+ sb.append("\nSignature digest " + i++ + "/" + noOfSigs + ":");
+ for (byte b : sig) {
+ sb.append(String.format(" %02X", b));
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int compareTo(AttestationApplicationId other) {
+ int res = Integer.compare(packageInfos.size(), other.packageInfos.size());
+ if (res != 0) return res;
+ for (int i = 0; i < packageInfos.size(); ++i) {
+ res = packageInfos.get(i).compareTo(other.packageInfos.get(i));
+ if (res != 0) return res;
+ }
+ res = Integer.compare(signatureDigests.size(), other.signatureDigests.size());
+ if (res != 0) return res;
+ ByteArrayComparator cmp = new ByteArrayComparator();
+ for (int i = 0; i < signatureDigests.size(); ++i) {
+ res = cmp.compare(signatureDigests.get(i), other.signatureDigests.get(i));
+ if (res != 0) return res;
+ }
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof AttestationApplicationId)
+ && (0 == compareTo((AttestationApplicationId) o));
+ }
+
+ private List<AttestationPackageInfo> parseAttestationPackageInfos(ASN1Encodable asn1Encodable)
+ throws CertificateParsingException {
+ if (!(asn1Encodable instanceof ASN1Set)) {
+ throw new CertificateParsingException(
+ "Expected set for AttestationApplicationsInfos, found "
+ + asn1Encodable.getClass().getName());
+ }
+
+ ASN1Set set = (ASN1Set) asn1Encodable;
+ List<AttestationPackageInfo> result = new ArrayList<AttestationPackageInfo>();
+ for (ASN1Encodable e : set) {
+ result.add(new AttestationPackageInfo(e));
+ }
+ return result;
+ }
+
+ private List<byte[]> parseSignatures(ASN1Encodable asn1Encodable)
+ throws CertificateParsingException {
+ if (!(asn1Encodable instanceof ASN1Set)) {
+ throw new CertificateParsingException("Expected set for Signature digests, found "
+ + asn1Encodable.getClass().getName());
+ }
+
+ ASN1Set set = (ASN1Set) asn1Encodable;
+ List<byte[]> result = new ArrayList<byte[]>();
+
+ for (ASN1Encodable e : set) {
+ result.add(Asn1Utils.getByteArrayFromAsn1(e));
+ }
+ return result;
+ }
+
+ private class ByteArrayComparator implements java.util.Comparator<byte[]> {
+ @Override
+ public int compare(byte[] a, byte[] b) {
+ int res = Integer.compare(a.length, b.length);
+ if (res != 0) return res;
+ for (int i = 0; i < a.length; ++i) {
+ res = Byte.compare(a[i], b[i]);
+ if (res != 0) return res;
+ }
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationPackageInfo.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationPackageInfo.java
new file mode 100644
index 0000000..3ccaf7a
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationPackageInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.attestationexample;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+import java.security.cert.CertificateParsingException;
+
+import java.io.UnsupportedEncodingException;
+
+public class AttestationPackageInfo implements java.lang.Comparable<AttestationPackageInfo> {
+ private static final int PACKAGE_NAME_INDEX = 0;
+ private static final int VERSION_INDEX = 1;
+
+ private final String packageName;
+ private final int version;
+
+ public AttestationPackageInfo(String packageName, int version) {
+ this.packageName = packageName;
+ this.version = version;
+ }
+
+ public AttestationPackageInfo(ASN1Encodable asn1Encodable) throws CertificateParsingException {
+ if (!(asn1Encodable instanceof ASN1Sequence)) {
+ throw new CertificateParsingException(
+ "Expected sequence for AttestationPackageInfo, found "
+ + asn1Encodable.getClass().getName());
+ }
+
+ ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
+ try {
+ packageName = Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(
+ sequence.getObjectAt(PACKAGE_NAME_INDEX));
+ } catch (UnsupportedEncodingException e) {
+ throw new CertificateParsingException(
+ "Converting octet stream to String triggered an UnsupportedEncodingException",
+ e);
+ }
+ version = Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERSION_INDEX));
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append("Package name: ").append(getPackageName())
+ .append("\nVersion: " + getVersion()).toString();
+ }
+
+ @Override
+ public int compareTo(AttestationPackageInfo other) {
+ int res = packageName.compareTo(other.packageName);
+ if (res != 0) return res;
+ res = Integer.compare(version, other.version);
+ if (res != 0) return res;
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof AttestationPackageInfo)
+ && (0 == compareTo((AttestationPackageInfo) o));
+ }
+}
\ No newline at end of file
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationTest.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationTest.java
new file mode 100644
index 0000000..287d065
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AttestationTest.java
@@ -0,0 +1,317 @@
+package com.google.attestationexample;
+
+import android.os.AsyncTask;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Base64;
+import android.util.Log;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import static android.security.keystore.KeyProperties.DIGEST_SHA256;
+import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
+import static com.google.attestationexample.RootOfTrust.KM_VERIFIED_BOOT_VERIFIED;
+
+/**
+ * AttestationTest generates an EC Key pair, with attestation, and displays the result in the
+ * TextView provided to its constructor.
+ */
+public class AttestationTest extends AsyncTask<Void, String, Void> {
+ private static final int ORIGINATION_TIME_OFFSET = 1000000;
+ private static final int CONSUMPTION_TIME_OFFSET = 2000000;
+
+ private static final int KEY_USAGE_BITSTRING_LENGTH = 9;
+ private static final int KEY_USAGE_DIGITAL_SIGNATURE_BIT_OFFSET = 0;
+ private static final int KEY_USAGE_KEY_ENCIPHERMENT_BIT_OFFSET = 2;
+ private static final int KEY_USAGE_DATA_ENCIPHERMENT_BIT_OFFSET = 3;
+
+ private static final int OS_MAJOR_VERSION_MATCH_GROUP_NAME = 1;
+ private static final int OS_MINOR_VERSION_MATCH_GROUP_NAME = 2;
+ private static final int OS_SUBMINOR_VERSION_MATCH_GROUP_NAME = 3;
+ private static final Pattern OS_VERSION_STRING_PATTERN = Pattern
+ .compile("([0-9]{1,2})(?:\\.([0-9]{1,2}))?(?:\\.([0-9]{1,2}))?(?:[^0-9.]+.*)?");
+
+ private static final int OS_PATCH_LEVEL_YEAR_GROUP_NAME = 1;
+ private static final int OS_PATCH_LEVEL_MONTH_GROUP_NAME = 2;
+ private static final Pattern OS_PATCH_LEVEL_STRING_PATTERN = Pattern
+ .compile("([0-9]{4})-([0-9]{2})-[0-9]{2}");
+
+ private static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
+
+ private static final String FINISHED = "AttestationFinished";
+ private static final String FAIL = "AttestationFail";
+ private static final String INFO = "AttestationInfo";
+ private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
+ private static final String KEY_USAGE_OID = "2.5.29.15"; // Standard key usage extension.
+
+ private static final String GOOGLE_ROOT_CERTIFICATE =
+ "-----BEGIN CERTIFICATE-----\n"
+ + "MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV"
+ + "BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy"
+ + "ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B"
+ + "AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS"
+ + "Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7"
+ + "tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj"
+ + "nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq"
+ + "C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ"
+ + "oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O"
+ + "JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg"
+ + "sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi"
+ + "igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M"
+ + "RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E"
+ + "aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um"
+ + "AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD"
+ + "VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO"
+ + "BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk"
+ + "Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD"
+ + "ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB"
+ + "Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m"
+ + "qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY"
+ + "DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm"
+ + "QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u"
+ + "JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD"
+ + "CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy"
+ + "ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD"
+ + "qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic"
+ + "MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1"
+ + "wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk\n"
+ + "-----END CERTIFICATE-----";
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ testEcAttestation();
+ } catch (Exception e) {
+ Log.e(FAIL, "Something is wrong, check detailed logcat logs:\n", e);
+ }
+ return null;
+ }
+
+ private void testEcAttestation() throws Exception {
+ String ecCurve = "secp256r1";
+ int keySize = 256;
+ byte[] challenge = "challenge".getBytes();
+ String keystoreAlias = "test_key";
+
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+
+ keyStore.deleteEntry(keystoreAlias);
+
+ Log.d(INFO,"Generating key pair...");
+ Date startTime = new Date(new Date().getTime() - 1000);
+ Log.d("****", "Start Time is: " + startTime.toString());
+ Date originationEnd = new Date(startTime.getTime() + ORIGINATION_TIME_OFFSET);
+ Date consumptionEnd = new Date(startTime.getTime() + CONSUMPTION_TIME_OFFSET);
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keystoreAlias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setAlgorithmParameterSpec(new ECGenParameterSpec(ecCurve))
+ .setDigests(DIGEST_SHA256)
+ .setAttestationChallenge(challenge);
+
+ builder.setKeyValidityStart(startTime)
+ .setKeyValidityForOriginationEnd(originationEnd)
+ .setKeyValidityForConsumptionEnd(consumptionEnd);
+
+ generateKeyPair(KEY_ALGORITHM_EC, builder.build());
+ Log.d(INFO, "Key pair generated\n\n");
+
+ Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
+ Log.d(INFO, "Retrieved certificate chain of length " + certificates.length + "\n");
+ try {
+ verifyCertificateSignatures(certificates);
+ } catch (GeneralSecurityException e) {
+ Log.e(FAIL, "Certificate chain does not verify.", e);
+ }
+
+ X509Certificate attestationCert = (X509Certificate) certificates[0];
+ X509Certificate secureRoot = (X509Certificate) CertificateFactory
+ .getInstance("X.509").generateCertificate(
+ new ByteArrayInputStream(
+ GOOGLE_ROOT_CERTIFICATE.getBytes()));
+ X509Certificate rootCert = (X509Certificate) certificates[certificates.length - 1];
+ if (!Arrays.equals(secureRoot.getPublicKey().getEncoded(),
+ rootCert.getPublicKey().getEncoded())) {
+ Log.e(FAIL, "root certificate public key does not match Google public key");
+ }
+ printKeyUsage(attestationCert);
+
+ ImmutableSet<String> unexpectedExtensionOids =
+ (ImmutableSet) retrieveUnexpectedExtensionOids(attestationCert);
+ if (!unexpectedExtensionOids.isEmpty()) {
+ Log.e(FAIL, "attestation certificate contains unexpected OIDs");
+ for (String oid : unexpectedExtensionOids.toArray(new String[unexpectedExtensionOids.size()])) {
+ Log.e(FAIL, "Unexpected OID: " + oid);
+ }
+ }
+
+ Attestation attestation = new Attestation(attestationCert);
+ if (!Arrays.equals(attestation.getAttestationChallenge(), challenge)) {
+ Utils.logError("challenge mismatch\nExpected:",
+ challenge, attestation.getAttestationChallenge());
+ }
+
+ if (attestation.getAttestationSecurityLevel() != 1) {
+ Utils.logError("Attestation cert reporting non-TEE security level",
+ 1, attestation.getAttestationSecurityLevel());
+ }
+ if (attestation.getKeymasterSecurityLevel() != 1) {
+ Utils.logError("Keymaster reporting non-TEE security level",
+ 1, attestation.getKeymasterSecurityLevel());
+ }
+
+ checkRootOfTrust(attestation);
+
+ Signature signer = Signature.getInstance("SHA256WithECDSA");
+ KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
+ keystore.load(null);
+
+ PrivateKey key = (PrivateKey) keystore.getKey(keystoreAlias, null);
+ try {
+ signer.initSign(key);
+ signer.update("Hello".getBytes());
+ signer.sign();
+ } catch(Exception e) {
+ Log.e(FAIL,"Failed to sign with generated key", e);
+ }
+ Log.d(FINISHED, "Signing completed");
+ }
+
+ private void generateKeyPair(String algorithm, KeyGenParameterSpec spec)
+ throws NoSuchAlgorithmException, NoSuchProviderException,
+ InvalidAlgorithmParameterException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm,
+ "AndroidKeyStore");
+ keyPairGenerator.initialize(spec);
+ keyPairGenerator.generateKeyPair();
+ }
+
+ private void verifyCertificateSignatures(Certificate[] certChain)
+ throws GeneralSecurityException {
+
+ for (Certificate cert : certChain) {
+ final byte[] derCert = cert.getEncoded();
+ final String pemCertPre = Base64.encodeToString(derCert, Base64.NO_WRAP);
+ Log.e("****", pemCertPre);
+ }
+
+ for (int i = 1; i < certChain.length; ++i) {
+ PublicKey pubKey = certChain[i].getPublicKey();
+ try {
+ certChain[i - 1].verify(pubKey);
+ } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
+ | NoSuchProviderException | SignatureException e) {
+ throw new GeneralSecurityException("Failed to verify certificate "
+ + certChain[i - 1] + " with public key " + certChain[i].getPublicKey(), e);
+ }
+ if (i == certChain.length - 1) {
+ // Last cert is self-signed.
+ try {
+ certChain[i].verify(pubKey);
+ } catch (CertificateException e) {
+ throw new GeneralSecurityException(
+ "Root cert " + certChain[i] + " is not correctly self-signed", e);
+ }
+ }
+ }
+ }
+
+ private void printKeyUsage(X509Certificate attestationCert) {
+ Log.d(INFO, "Key usage:");
+ if (attestationCert.getKeyUsage() == null) {
+ Log.d(INFO, " NONE\n");
+ return;
+ }
+ if (attestationCert.getKeyUsage()[KEY_USAGE_DIGITAL_SIGNATURE_BIT_OFFSET]) {
+ Log.d(INFO, " sign");
+ }
+ if (attestationCert.getKeyUsage()[KEY_USAGE_DATA_ENCIPHERMENT_BIT_OFFSET]) {
+ Log.d(INFO, " encrypt_data");
+ }
+ if (attestationCert.getKeyUsage()[KEY_USAGE_KEY_ENCIPHERMENT_BIT_OFFSET]) {
+ Log.d(INFO, " encrypt_keys");
+ }
+ Log.d(INFO, "\n");
+ }
+
+ private Set<String> retrieveUnexpectedExtensionOids(X509Certificate x509Cert) {
+ return new ImmutableSet.Builder<String>()
+ .addAll(x509Cert.getCriticalExtensionOIDs()
+ .stream()
+ .filter(s -> !KEY_USAGE_OID.equals(s))
+ .iterator())
+ .addAll(x509Cert.getNonCriticalExtensionOIDs()
+ .stream()
+ .filter(s -> !KEY_DESCRIPTION_OID.equals(s))
+ .iterator())
+ .build();
+ }
+
+
+ private void checkRootOfTrust(Attestation attestation) {
+ RootOfTrust rootOfTrust = attestation.getTeeEnforced().getRootOfTrust();
+ if (rootOfTrust == null) {
+ Log.e(FAIL, "Root of trust is null");
+ return;
+ }
+ if (rootOfTrust.getVerifiedBootKey() == null) {
+ Log.e(FAIL, "Verified boot key is null");
+ return;
+ }
+ if (rootOfTrust.getVerifiedBootKey().length < 32) {
+ Log.e(FAIL, "Verified boot key is less than 32 bytes");
+ }
+ if (isAllZeroes(rootOfTrust.getVerifiedBootKey())) {
+ Log.e(FAIL, "Verified boot key is all zeros.");
+ }
+ if (!rootOfTrust.isDeviceLocked()) {
+ Log.e(FAIL, "Device isn't locked");
+ }
+ if (KM_VERIFIED_BOOT_VERIFIED != rootOfTrust.getVerifiedBootState()) {
+ Utils.logError("Root of trust isn't reporting boot verified.",
+ KM_VERIFIED_BOOT_VERIFIED, rootOfTrust.getVerifiedBootState());
+ }
+ if (rootOfTrust.getVerifiedBootHash() == null) {
+ Log.e(FAIL, "Verified boot hash is null");
+ return;
+ }
+ if (rootOfTrust.getVerifiedBootHash().length != 32) {
+ Log.e(FAIL, "Verified boot hash isn't 32 bytes");
+ }
+ if (isAllZeroes(rootOfTrust.getVerifiedBootHash())) {
+ Log.e(FAIL, "Verified boot hash is all zeros.");
+ }
+ }
+
+ private boolean isAllZeroes(byte[] checkArray) {
+ for (byte b : checkArray) {
+ if (b != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/AuthorizationList.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AuthorizationList.java
new file mode 100644
index 0000000..4eb95e0
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/AuthorizationList.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.attestationexample;
+
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+
+import java.io.IOException;
+import java.security.cert.CertificateParsingException;
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Functions.forMap;
+import static com.google.common.collect.Collections2.transform;
+
+public class AuthorizationList {
+ // Algorithm values.
+ public static final int KM_ALGORITHM_RSA = 1;
+ public static final int KM_ALGORITHM_EC = 3;
+
+ // EC Curves
+ public static final int KM_EC_CURVE_P224 = 0;
+ public static final int KM_EC_CURVE_P256 = 1;
+ public static final int KM_EC_CURVE_P384 = 2;
+ public static final int KM_EC_CURVE_P521 = 3;
+
+ // Padding modes.
+ public static final int KM_PAD_NONE = 1;
+ public static final int KM_PAD_RSA_OAEP = 2;
+ public static final int KM_PAD_RSA_PSS = 3;
+ public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
+ public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
+
+ // Digest modes.
+ public static final int KM_DIGEST_NONE = 0;
+ public static final int KM_DIGEST_MD5 = 1;
+ public static final int KM_DIGEST_SHA1 = 2;
+ public static final int KM_DIGEST_SHA_2_224 = 3;
+ public static final int KM_DIGEST_SHA_2_256 = 4;
+ public static final int KM_DIGEST_SHA_2_384 = 5;
+ public static final int KM_DIGEST_SHA_2_512 = 6;
+
+ // Key origins.
+ public static final int KM_ORIGIN_GENERATED = 0;
+ public static final int KM_ORIGIN_IMPORTED = 2;
+ public static final int KM_ORIGIN_UNKNOWN = 3;
+
+ // Operation Purposes.
+ public static final int KM_PURPOSE_ENCRYPT = 0;
+ public static final int KM_PURPOSE_DECRYPT = 1;
+ public static final int KM_PURPOSE_SIGN = 2;
+ public static final int KM_PURPOSE_VERIFY = 3;
+ public static final int KM_PURPOSE_DERIVE_KEY = 4;
+ public static final int KM_PURPOSE_WRAP_KEY = 5;
+
+ // User authenticators.
+ public static final int HW_AUTH_PASSWORD = 1 << 0;
+ public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+
+ // Keymaster tag classes
+ private static final int KM_ENUM = 1 << 28;
+ private static final int KM_ENUM_REP = 2 << 28;
+ private static final int KM_UINT = 3 << 28;
+ private static final int KM_ULONG = 5 << 28;
+ private static final int KM_DATE = 6 << 28;
+ private static final int KM_BOOL = 7 << 28;
+ private static final int KM_BYTES = 9 << 28;
+
+ // Tag class removal mask
+ private static final int KEYMASTER_TAG_TYPE_MASK = 0x0FFFFFFF;
+
+ // Keymaster tags
+ private static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
+ private static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
+ private static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
+ private static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
+ private static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
+ private static final int KM_TAG_EC_CURVE = KM_ENUM | 10;
+ private static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
+ private static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
+ private static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
+ private static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
+ private static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
+ private static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
+ private static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
+ private static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
+ private static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
+ private static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
+ private static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
+ private static final int KM_TAG_ORIGIN = KM_ENUM | 702;
+ private static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
+ private static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+ private static final int KM_TAG_OS_VERSION = KM_UINT | 705;
+ private static final int KM_TAG_OS_PATCHLEVEL = KM_UINT | 706;
+ private static final int KM_TAG_ATTESTATION_APPLICATION_ID = KM_BYTES | 709;
+ private static final int KM_TAG_VENDOR_PATCHLEVEL = KM_UINT | 718;
+ private static final int KM_TAG_BOOT_PATCHLEVEL = KM_UINT | 719;
+
+ // Map for converting padding values to strings
+ private static final ImmutableMap<Integer, String> paddingMap = ImmutableMap
+ .<Integer, String> builder()
+ .put(KM_PAD_NONE, "NONE")
+ .put(KM_PAD_RSA_OAEP, "OAEP")
+ .put(KM_PAD_RSA_PSS, "PSS")
+ .put(KM_PAD_RSA_PKCS1_1_5_ENCRYPT, "PKCS1 ENCRYPT")
+ .put(KM_PAD_RSA_PKCS1_1_5_SIGN, "PKCS1 SIGN")
+ .build();
+
+ // Map for converting digest values to strings
+ private static final ImmutableMap<Integer, String> digestMap = ImmutableMap
+ .<Integer, String> builder()
+ .put(KM_DIGEST_NONE, "NONE")
+ .put(KM_DIGEST_MD5, "MD5")
+ .put(KM_DIGEST_SHA1, "SHA1")
+ .put(KM_DIGEST_SHA_2_224, "SHA224")
+ .put(KM_DIGEST_SHA_2_256, "SHA256")
+ .put(KM_DIGEST_SHA_2_384, "SHA384")
+ .put(KM_DIGEST_SHA_2_512, "SHA512")
+ .build();
+
+ // Map for converting purpose values to strings
+ private static final ImmutableMap<Integer, String> purposeMap = ImmutableMap
+ .<Integer, String> builder()
+ .put(KM_PURPOSE_DECRYPT, "DECRYPT")
+ .put(KM_PURPOSE_ENCRYPT, "ENCRYPT")
+ .put(KM_PURPOSE_SIGN, "SIGN")
+ .put(KM_PURPOSE_VERIFY, "VERIFY")
+ .build();
+
+ private Set<Integer> purposes;
+ private Integer algorithm;
+ private Integer keySize;
+ private Set<Integer> digests;
+ private Set<Integer> paddingModes;
+ private Integer ecCurve;
+ private Long rsaPublicExponent;
+ private Date activeDateTime;
+ private Date originationExpireDateTime;
+ private Date usageExpireDateTime;
+ private boolean noAuthRequired;
+ private Integer userAuthType;
+ private Integer authTimeout;
+ private boolean allowWhileOnBody;
+ private boolean allApplications;
+ private byte[] applicationId;
+ private Date creationDateTime;
+ private Integer origin;
+ private boolean rollbackResistant;
+ private RootOfTrust rootOfTrust;
+ private Integer osVersion;
+ private Integer osPatchLevel;
+ private Integer vendorPatchLevel;
+ private Integer bootPatchLevel;
+ private AttestationApplicationId attestationApplicationId;
+
+ public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingException {
+ if (!(sequence instanceof ASN1Sequence)) {
+ throw new CertificateParsingException("Expected sequence for authorization list, found "
+ + sequence.getClass().getName());
+ }
+
+ ASN1SequenceParser parser = ((ASN1Sequence) sequence).parser();
+ ASN1TaggedObject entry = parseAsn1TaggedObject(parser);
+ for (; entry != null; entry = parseAsn1TaggedObject(parser)) {
+ int tag = entry.getTagNo();
+ ASN1Primitive value = entry.getObject();
+ Log.i("Attestation", "Parsing tag: [" + tag + "], value: [" + value + "]");
+ switch (tag) {
+ default:
+ throw new CertificateParsingException("Unknown tag " + tag + " found");
+
+ case KM_TAG_PURPOSE & KEYMASTER_TAG_TYPE_MASK:
+ purposes = Asn1Utils.getIntegersFromAsn1Set(value);
+ break;
+ case KM_TAG_ALGORITHM & KEYMASTER_TAG_TYPE_MASK:
+ algorithm = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_KEY_SIZE & KEYMASTER_TAG_TYPE_MASK:
+ keySize = Asn1Utils.getIntegerFromAsn1(value);
+ Log.i("Attestation", "Found KEY SIZE, value: " + keySize);
+ break;
+ case KM_TAG_DIGEST & KEYMASTER_TAG_TYPE_MASK:
+ digests = Asn1Utils.getIntegersFromAsn1Set(value);
+ break;
+ case KM_TAG_PADDING & KEYMASTER_TAG_TYPE_MASK:
+ paddingModes = Asn1Utils.getIntegersFromAsn1Set(value);
+ break;
+ case KM_TAG_RSA_PUBLIC_EXPONENT & KEYMASTER_TAG_TYPE_MASK:
+ rsaPublicExponent = Asn1Utils.getLongFromAsn1(value);
+ break;
+ case KM_TAG_NO_AUTH_REQUIRED & KEYMASTER_TAG_TYPE_MASK:
+ noAuthRequired = true;
+ break;
+ case KM_TAG_CREATION_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+ creationDateTime = Asn1Utils.getDateFromAsn1(value);
+ break;
+ case KM_TAG_ORIGIN & KEYMASTER_TAG_TYPE_MASK:
+ origin = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_OS_VERSION & KEYMASTER_TAG_TYPE_MASK:
+ osVersion = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_OS_PATCHLEVEL & KEYMASTER_TAG_TYPE_MASK:
+ osPatchLevel = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_VENDOR_PATCHLEVEL & KEYMASTER_TAG_TYPE_MASK:
+ vendorPatchLevel = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_BOOT_PATCHLEVEL & KEYMASTER_TAG_TYPE_MASK:
+ bootPatchLevel = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_ACTIVE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+ activeDateTime = Asn1Utils.getDateFromAsn1(value);
+ break;
+ case KM_TAG_ORIGINATION_EXPIRE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+ originationExpireDateTime = Asn1Utils.getDateFromAsn1(value);
+ break;
+ case KM_TAG_USAGE_EXPIRE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+ usageExpireDateTime = Asn1Utils.getDateFromAsn1(value);
+ break;
+ case KM_TAG_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
+ applicationId = Asn1Utils.getByteArrayFromAsn1(value);
+ break;
+ case KM_TAG_ROLLBACK_RESISTANT & KEYMASTER_TAG_TYPE_MASK:
+ rollbackResistant = true;
+ break;
+ case KM_TAG_AUTH_TIMEOUT & KEYMASTER_TAG_TYPE_MASK:
+ authTimeout = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_ALLOW_WHILE_ON_BODY & KEYMASTER_TAG_TYPE_MASK:
+ allowWhileOnBody = true;
+ break;
+ case KM_TAG_EC_CURVE & KEYMASTER_TAG_TYPE_MASK:
+ ecCurve = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_USER_AUTH_TYPE & KEYMASTER_TAG_TYPE_MASK:
+ userAuthType = Asn1Utils.getIntegerFromAsn1(value);
+ break;
+ case KM_TAG_ROOT_OF_TRUST & KEYMASTER_TAG_TYPE_MASK:
+ rootOfTrust = new RootOfTrust(value);
+ break;
+ case KM_TAG_ATTESTATION_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
+ attestationApplicationId = new AttestationApplicationId(Asn1Utils
+ .getAsn1EncodableFromBytes(Asn1Utils.getByteArrayFromAsn1(value)));
+ break;
+ case KM_TAG_ALL_APPLICATIONS & KEYMASTER_TAG_TYPE_MASK:
+ allApplications = true;
+ break;
+ }
+ }
+
+ }
+
+ public static String algorithmToString(int algorithm) {
+ switch (algorithm) {
+ case KM_ALGORITHM_RSA:
+ return "RSA";
+ case KM_ALGORITHM_EC:
+ return "ECDSA";
+ default:
+ return "Unknown";
+ }
+ }
+
+ public static String paddingModesToString(final Set<Integer> paddingModes) {
+ return joinStrings(transform(paddingModes, forMap(paddingMap, "Unknown")));
+ }
+
+ public static String paddingModeToString(int paddingMode) {
+ return forMap(paddingMap, "Unknown").apply(paddingMode);
+ }
+
+ public static String digestsToString(Set<Integer> digests) {
+ return joinStrings(transform(digests, forMap(digestMap, "Unknown")));
+ }
+
+ public static String digestToString(int digest) {
+ return forMap(digestMap, "Unknown").apply(digest);
+ }
+
+ public static String purposesToString(Set<Integer> purposes) {
+ return joinStrings(transform(purposes, forMap(purposeMap, "Unknown")));
+ }
+
+ public static String userAuthTypeToString(int userAuthType) {
+ List<String> types = Lists.newArrayList();
+ if ((userAuthType & HW_AUTH_FINGERPRINT) != 0)
+ types.add("Fingerprint");
+ if ((userAuthType & HW_AUTH_PASSWORD) != 0)
+ types.add("Password");
+ return joinStrings(types);
+ }
+
+ public static String originToString(int origin) {
+ switch (origin) {
+ case KM_ORIGIN_GENERATED:
+ return "Generated";
+ case KM_ORIGIN_IMPORTED:
+ return "Imported";
+ case KM_ORIGIN_UNKNOWN:
+ return "Unknown (KM0)";
+ default:
+ return "Unknown";
+ }
+ }
+
+ private static String joinStrings(Collection<String> collection) {
+ return new StringBuilder()
+ .append("[")
+ .append(Joiner.on(", ").join(collection))
+ .append("]")
+ .toString();
+ }
+
+ private static String formatDate(Date date) {
+ return DateFormat.getDateTimeInstance().format(date);
+ }
+
+ private static ASN1TaggedObject parseAsn1TaggedObject(ASN1SequenceParser parser)
+ throws CertificateParsingException {
+ ASN1Encodable asn1Encodable = parseAsn1Encodable(parser);
+ if (asn1Encodable == null || asn1Encodable instanceof ASN1TaggedObject) {
+ return (ASN1TaggedObject) asn1Encodable;
+ }
+ throw new CertificateParsingException(
+ "Expected tagged object, found " + asn1Encodable.getClass().getName());
+ }
+
+ private static ASN1Encodable parseAsn1Encodable(ASN1SequenceParser parser)
+ throws CertificateParsingException {
+ try {
+ return parser.readObject();
+ } catch (IOException e) {
+ throw new CertificateParsingException("Failed to parse ASN1 sequence", e);
+ }
+ }
+
+ public Set<Integer> getPurposes() {
+ return purposes;
+ }
+
+ public Integer getAlgorithm() {
+ return algorithm;
+ }
+
+ public Integer getKeySize() {
+ return keySize;
+ }
+
+ public Set<Integer> getDigests() {
+ return digests;
+ }
+
+ public Set<Integer> getPaddingModes() {
+ return paddingModes;
+ }
+
+ public Set<String> getPaddingModesAsStrings() throws CertificateParsingException {
+ if (paddingModes == null) {
+ return ImmutableSet.of();
+ }
+
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for (int paddingMode : paddingModes) {
+ switch (paddingMode) {
+ case KM_PAD_NONE:
+ builder.add(KeyProperties.ENCRYPTION_PADDING_NONE);
+ break;
+ case KM_PAD_RSA_OAEP:
+ builder.add(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
+ break;
+ case KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ builder.add(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
+ break;
+ case KM_PAD_RSA_PKCS1_1_5_SIGN:
+ builder.add(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+ break;
+ case KM_PAD_RSA_PSS:
+ builder.add(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+ break;
+ default:
+ throw new CertificateParsingException("Invalid padding mode " + paddingMode);
+ }
+ }
+ return builder.build();
+ }
+
+ public Integer getEcCurve() {
+ return ecCurve;
+ }
+
+ public String ecCurveAsString() {
+ if (ecCurve == null)
+ return "NULL";
+
+ switch (ecCurve) {
+ case KM_EC_CURVE_P224:
+ return "secp224r1";
+ case KM_EC_CURVE_P256:
+ return "secp256r1";
+ case KM_EC_CURVE_P384:
+ return "secp384r1";
+ case KM_EC_CURVE_P521:
+ return "secp521r1";
+ default:
+ return "unknown";
+ }
+ }
+
+ public Long getRsaPublicExponent() {
+ return rsaPublicExponent;
+ }
+
+ public Date getActiveDateTime() {
+ return activeDateTime;
+ }
+
+ public Date getOriginationExpireDateTime() {
+ return originationExpireDateTime;
+ }
+
+ public Date getUsageExpireDateTime() {
+ return usageExpireDateTime;
+ }
+
+ public boolean isNoAuthRequired() {
+ return noAuthRequired;
+ }
+
+ public Integer getUserAuthType() {
+ return userAuthType;
+ }
+
+ public Integer getAuthTimeout() {
+ return authTimeout;
+ }
+
+ public boolean isAllowWhileOnBody() {
+ return allowWhileOnBody;
+ }
+
+ public boolean isAllApplications() {
+ return allApplications;
+ }
+
+ public byte[] getApplicationId() {
+ return applicationId;
+ }
+
+ public Date getCreationDateTime() {
+ return creationDateTime;
+ }
+
+ public Integer getOrigin() {
+ return origin;
+ }
+
+ public boolean isRollbackResistant() {
+ return rollbackResistant;
+ }
+
+ public RootOfTrust getRootOfTrust() {
+ return rootOfTrust;
+ }
+
+ public Integer getOsVersion() {
+ return osVersion;
+ }
+
+ public Integer getOsPatchLevel() {
+ return osPatchLevel;
+ }
+
+ public Integer getVendorPatchLevel() { return vendorPatchLevel; }
+
+ public Integer getBootPatchLevel() { return bootPatchLevel; }
+
+ public AttestationApplicationId getAttestationApplicationId() {
+ return attestationApplicationId;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+
+ if (algorithm != null) {
+ s.append("\nAlgorithm: ").append(algorithmToString(algorithm));
+ }
+
+ if (keySize != null) {
+ s.append("\nKeySize: ").append(keySize);
+ }
+
+ if (purposes != null && !purposes.isEmpty()) {
+ s.append("\nPurposes: ").append(purposesToString(purposes));
+ }
+
+ if (digests != null && !digests.isEmpty()) {
+ s.append("\nDigests: ").append(digestsToString(digests));
+ }
+
+ if (paddingModes != null && !paddingModes.isEmpty()) {
+ s.append("\nPadding modes: ").append(paddingModesToString(paddingModes));
+ }
+
+ if (ecCurve != null) {
+ s.append("\nEC Curve: ").append(ecCurveAsString());
+ }
+
+ String label = "\nRSA exponent: ";
+ if (rsaPublicExponent != null) {
+ s.append(label).append(rsaPublicExponent);
+ }
+
+ if (activeDateTime != null) {
+ s.append("\nActive: ").append(formatDate(activeDateTime));
+ }
+
+ if (originationExpireDateTime != null) {
+ s.append("\nOrigination expire: ").append(formatDate(originationExpireDateTime));
+ }
+
+ if (usageExpireDateTime != null) {
+ s.append("\nUsage expire: ").append(formatDate(usageExpireDateTime));
+ }
+
+ if (!noAuthRequired && userAuthType != null) {
+ s.append("\nAuth types: ").append(userAuthTypeToString(userAuthType));
+ if (authTimeout != null) {
+ s.append("\nAuth timeout: ").append(authTimeout);
+ }
+ }
+
+ if (applicationId != null) {
+ s.append("\nApplication ID: ").append(new String(applicationId));
+ }
+
+ if (creationDateTime != null) {
+ s.append("\nCreated: ").append(formatDate(creationDateTime));
+ }
+
+ if (origin != null) {
+ s.append("\nOrigin: ").append(originToString(origin));
+ }
+
+ if (rollbackResistant) {
+ s.append("\nRollback resistant: true");
+ }
+
+ if (rootOfTrust != null) {
+ s.append("\nRoot of Trust:\n");
+ s.append(rootOfTrust);
+ }
+
+ if (osVersion != null) {
+ s.append("\nOS Version: ").append(osVersion);
+ }
+
+ if (osPatchLevel != null) {
+ s.append("\nOS Patchlevel: ").append(osPatchLevel);
+ }
+
+ if (vendorPatchLevel != null) {
+ s.append("\nVendor Patchlevel: ").append(vendorPatchLevel);
+ }
+
+ if (bootPatchLevel != null) {
+ s.append("\nBoot Patchlevel: ").append(bootPatchLevel);
+ }
+
+ if (attestationApplicationId != null) {
+ s.append("\nApplication ID:").append(attestationApplicationId.toString());
+ }
+
+ return s.toString();
+ }
+}
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/RootOfTrust.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/RootOfTrust.java
new file mode 100644
index 0000000..34d9aa3
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/RootOfTrust.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.attestationexample;
+
+import com.google.common.io.BaseEncoding;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+import java.security.cert.CertificateParsingException;
+
+public class RootOfTrust {
+ private static final int VERIFIED_BOOT_KEY_INDEX = 0;
+ private static final int DEVICE_LOCKED_INDEX = 1;
+ private static final int VERIFIED_BOOT_STATE_INDEX = 2;
+ private static final int VERIFIED_BOOT_HASH_INDEX = 3;
+
+ public static final int KM_VERIFIED_BOOT_VERIFIED = 0;
+ public static final int KM_VERIFIED_BOOT_SELF_SIGNED = 1;
+ public static final int KM_VERIFIED_BOOT_UNVERIFIED = 2;
+ public static final int KM_VERIFIED_BOOT_FAILED = 3;
+
+ private final byte[] verifiedBootKey;
+ private final byte[] verifiedBootHash;
+ private final boolean deviceLocked;
+ private final int verifiedBootState;
+
+ public RootOfTrust(ASN1Encodable asn1Encodable) throws CertificateParsingException {
+ if (!(asn1Encodable instanceof ASN1Sequence)) {
+ throw new CertificateParsingException("Expected sequence for root of trust, found "
+ + asn1Encodable.getClass().getName());
+ }
+
+ ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
+ verifiedBootKey =
+ Asn1Utils.getByteArrayFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_KEY_INDEX));
+ deviceLocked = Asn1Utils.getBooleanFromAsn1(sequence.getObjectAt(DEVICE_LOCKED_INDEX));
+ verifiedBootState =
+ Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_STATE_INDEX));
+ verifiedBootHash =
+ Asn1Utils.getByteArrayFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_HASH_INDEX));
+ }
+
+ public static String verifiedBootStateToString(int verifiedBootState) {
+ switch (verifiedBootState) {
+ case KM_VERIFIED_BOOT_VERIFIED:
+ return "Verified";
+ case KM_VERIFIED_BOOT_SELF_SIGNED:
+ return "Self-signed";
+ case KM_VERIFIED_BOOT_UNVERIFIED:
+ return "Unverified";
+ case KM_VERIFIED_BOOT_FAILED:
+ return "Failed";
+ default:
+ return "Unknown";
+ }
+ }
+
+ public byte[] getVerifiedBootKey() {
+ return verifiedBootKey;
+ }
+
+ public boolean isDeviceLocked() {
+ return deviceLocked;
+ }
+
+ public int getVerifiedBootState() {
+ return verifiedBootState;
+ }
+
+ public byte[] getVerifiedBootHash() { return verifiedBootHash; }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("Verified boot Key: ")
+ .append(BaseEncoding.base64().encode(verifiedBootKey))
+ .append("\nDevice locked: ")
+ .append(deviceLocked)
+ .append("\nVerified boot state: ")
+ .append(verifiedBootStateToString(verifiedBootState))
+ .append("\nVerified boot hash: ")
+ .append(BaseEncoding.base64().encode(verifiedBootHash))
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/remote_provisioning/attestation_testing/java/com/google/attestationexample/Utils.java b/remote_provisioning/attestation_testing/java/com/google/attestationexample/Utils.java
new file mode 100644
index 0000000..e13d8a8
--- /dev/null
+++ b/remote_provisioning/attestation_testing/java/com/google/attestationexample/Utils.java
@@ -0,0 +1,22 @@
+package com.google.attestationexample;
+
+import android.util.Log;
+
+import java.util.Arrays;
+
+public class Utils {
+ public static final String FAIL = "AttestationFail";
+ public static final String FAIL_INFO = "AttestationFailInfo";
+
+ public static void logError(String message, int expected, int actual) {
+ Log.e(FAIL, message);
+ Log.e(FAIL_INFO, "Expected: " + expected);
+ Log.e(FAIL_INFO, "Actual: " + actual);
+ }
+
+ public static void logError(String message, byte[] expected, byte[] actual) {
+ Log.e(FAIL, message);
+ Log.e(FAIL_INFO, "Expected: " + Arrays.toString(expected));
+ Log.e(FAIL_INFO, "Actual: " + Arrays.toString(actual));
+ }
+}
diff --git a/remote_provisioning/attestation_testing/res/layout/activity_attestation.xml b/remote_provisioning/attestation_testing/res/layout/activity_attestation.xml
new file mode 100644
index 0000000..4aaffed
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/layout/activity_attestation.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context="com.google.attestationexample.AttestationActivity">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/content_attestation" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/fab_margin"
+ app:srcCompat="@android:drawable/btn_star_big_on" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/remote_provisioning/attestation_testing/res/layout/content_attestation.xml b/remote_provisioning/attestation_testing/res/layout/content_attestation.xml
new file mode 100644
index 0000000..9759ef2
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/layout/content_attestation.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/content_attestation"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="com.google.attestationexample.AttestationActivity"
+ tools:showIn="@layout/activity_attestation">
+
+ <TextView
+ android:id="@+id/textview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="10000"
+ android:scrollbars="vertical"
+ android:text=""
+ android:scrollbarAlwaysDrawVerticalTrack="false"
+ android:scrollbarAlwaysDrawHorizontalTrack="false"
+ android:scrollHorizontally="false" />
+</RelativeLayout>
diff --git a/remote_provisioning/attestation_testing/res/menu/menu_attestation.xml b/remote_provisioning/attestation_testing/res/menu/menu_attestation.xml
new file mode 100644
index 0000000..9a34152
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/menu/menu_attestation.xml
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="com.google.attestationexample.AttestationActivity">
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:title="@string/action_settings"
+ app:showAsAction="never" />
+</menu>
diff --git a/remote_provisioning/attestation_testing/res/mipmap-hdpi/ic_launcher.png b/remote_provisioning/attestation_testing/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/remote_provisioning/attestation_testing/res/values-v21/styles.xml b/remote_provisioning/attestation_testing/res/values-v21/styles.xml
new file mode 100644
index 0000000..dbbdd40
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/values-v21/styles.xml
@@ -0,0 +1,9 @@
+<resources>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+</resources>
diff --git a/remote_provisioning/attestation_testing/res/values-w820dp/dimens.xml b/remote_provisioning/attestation_testing/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/remote_provisioning/attestation_testing/res/values/colors.xml b/remote_provisioning/attestation_testing/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/remote_provisioning/attestation_testing/res/values/dimens.xml b/remote_provisioning/attestation_testing/res/values/dimens.xml
new file mode 100644
index 0000000..812cb7b
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/values/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/remote_provisioning/attestation_testing/res/values/strings.xml b/remote_provisioning/attestation_testing/res/values/strings.xml
new file mode 100644
index 0000000..e6cb24b
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/values/strings.xml
@@ -0,0 +1,4 @@
+<resources>
+ <string name="app_name">AttestationExample</string>
+ <string name="action_settings">Settings</string>
+</resources>
diff --git a/remote_provisioning/attestation_testing/res/values/styles.xml b/remote_provisioning/attestation_testing/res/values/styles.xml
new file mode 100644
index 0000000..545b9c6
--- /dev/null
+++ b/remote_provisioning/attestation_testing/res/values/styles.xml
@@ -0,0 +1,20 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
+ <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+ <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>