From nobody Mon Jun 8 04:19:59 2026 Received: from mail-dy1-f202.google.com (mail-dy1-f202.google.com [74.125.82.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4A8D13B841F for ; Tue, 2 Jun 2026 07:32:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780385546; cv=none; b=q+7XTVs2/987sqg7L4qXEkxSHtGYbjELZzZEPdqIIgbRB7krOCALGpMKKSan5pOZBqPzax+44O28f3l0+J/eJIF6QvHJBNzC4P6rUnkQ+avrIVqSW3Vq/cdGuWTBP9RbRqU3teAM5BYduaoFS53IUPXCwWBZ0Uh+dtOcglULfhk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780385546; c=relaxed/simple; bh=mWIanpKeDH30sSrD936HsMJZof5t57jUwEPIRP23LfU=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=PG5hCu8othZG2YA3PHTw3xIXwH/+LbUm0h8Ye4UBWd+MDGfS7CoMDbVoC3tolO1sMTmgkDjiPMHQqZnWNFRIDMRlXifKJvpZdRhBo1P85Gg1gSVybYNYKCbgWjOov1il/PfgmTrrdk6Vw91NkuNtSdKEzJhRSxVDYV3Z+g+m9Gs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=jWonLXED; arc=none smtp.client-ip=74.125.82.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="jWonLXED" Received: by mail-dy1-f202.google.com with SMTP id 5a478bee46e88-304d0d0b28eso4396027eec.0 for ; Tue, 02 Jun 2026 00:32:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780385533; x=1780990333; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=YerWkdeuJmRVjY8ybF/YzIF49I+Kuz4nHQWOPDocD1w=; b=jWonLXEDuzkfeYv0gbaKuyWTNbThAxcqc7WLE9C972ZB/hyl58kG0Qnz8bnS3R5tWs QOi7ThVoxdxXB/7m4RsgGmD/v9pwmRs6NbiulamSpIfftI9OfUFIOdfr3qJzi7nTiBbn +K1LSEQUrniHRGoagZQklmKF0wYQMmnd6+JPQPEOXzyh42tRqyXDvhteYpuBjiK7em07 1D7Z7E+fjJqHA71j49Ins+2IfsxPFyKXY/2UmUJReDxA9ypw2gDatnQHzsa6Zd5Zg4YP t3Wukrf1r6TT5VjJCZ09YIxDCvzcWkQpaAwUPlonQ+ftRH4qaSLYryRX/NggKYW3e+sy ALEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780385533; x=1780990333; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=YerWkdeuJmRVjY8ybF/YzIF49I+Kuz4nHQWOPDocD1w=; b=L57t4welAU0JBFeboUUyGClbM4myYB5UPN3Wr/L9OIBo+9QtEYp5FFbNwkwqwztWqK NdfwLc9qOJrgtxURMHUBbB8BezmKn3Q/GkVaawegH7CZlclZ0Ybtm1+w5/GoCehjN/WM 3rE4Z4d33ogvx+rfpWrIzX4X3WImMBUrzvAiLxChscQdUZYnjEMQfNGmQFWx60NV3/Gb KC9fV3d/ekUDCQ1QJzrOXJBH17lgjST6/AC19sNw6M5xs5dg/kFBey6dol47Whbyo3a5 b7MXBawojn6CwMiN8FnTcwHLgqMn7mpQXft+IRttdwnWPsL2tpSkVLf3g78LqsAxZ4A0 jl2g== X-Forwarded-Encrypted: i=1; AFNElJ/YG93q5i9O4SyXcvI+sSP5JuCjziy8gMA93oKdEiENo+i6adi34jw5zTgMOZQfSwQCIpwZhY3TlEil8+o=@vger.kernel.org X-Gm-Message-State: AOJu0YxoTmY490XqymyBqIuWkQfQwGaKp3Lo5QZGXHew23piZDDNxtEQ c0OxQfFEhagGXfwJrh4bjAI0lqUarAPv+IFwxUgNCAfQkgqpNK6UltV3X33WfG7BLwN28n0fM12 ZegyqR4CrCQ== X-Received: from dlec13-n1.prod.google.com ([2002:a05:701b:428d:10b0:136:1d52:aebc]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7300:7fa4:b0:304:de8e:17b with SMTP id 5a478bee46e88-304fa49cdf3mr7645263eec.5.1780385533107; Tue, 02 Jun 2026 00:32:13 -0700 (PDT) Date: Tue, 2 Jun 2026 00:31:31 -0700 In-Reply-To: <20260602073132.2653307-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260601061401.1541457-1-irogers@google.com> <20260602073132.2653307-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.929.g9b7fa37559-goog Message-ID: <20260602073132.2653307-17-irogers@google.com> Subject: [PATCH v7] perf test: Remove /usr/bin/cc dependency from Intel PT shell test From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, alexander.shishkin@linux.intel.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In test_intel_pt.sh, the test script compiled two external C programs at runtime using /usr/bin/cc (a thread loop workload and a JIT self- modifying workload). Relying on external C compilers inside shell tests frequently causes failures in continuous integration environments. Create a built-in 'jitdump' workload and switch test_intel_pt.sh to use 'perf test -w thloop' and 'perf test -w jitdump'. Also add multi- architecture compatibility without external C compiler dependencies, the workload instruction arrays dynamically encode CHK_BYTE into opcodes across x86, ARM32, ARM64, RISC-V, PowerPC, MIPS, LoongArch, and s390x. Some minor include fixes for util/jitdump.h. Assisted-by: Gemini-CLI:Google Gemini 3 Signed-off-by: Ian Rogers --- tools/perf/tests/builtin-test.c | 1 + tools/perf/tests/shell/test_intel_pt.sh | 169 +------------------- tools/perf/tests/tests.h | 1 + tools/perf/tests/workloads/Build | 1 + tools/perf/tests/workloads/jitdump.c | 201 ++++++++++++++++++++++++ tools/perf/util/jitdump.h | 3 +- 6 files changed, 208 insertions(+), 168 deletions(-) create mode 100644 tools/perf/tests/workloads/jitdump.c diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-tes= t.c index 6a2bce394325..58381a50bbcc 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -161,6 +161,7 @@ static struct test_workload *workloads[] =3D { &workload__landlock, &workload__traploop, &workload__inlineloop, + &workload__jitdump, =20 #ifdef HAVE_RUST_SUPPORT &workload__code_with_type, diff --git a/tools/perf/tests/shell/test_intel_pt.sh b/tools/perf/tests/she= ll/test_intel_pt.sh index 8ee761f03c38..26243ff760ec 100755 --- a/tools/perf/tests/shell/test_intel_pt.sh +++ b/tools/perf/tests/shell/test_intel_pt.sh @@ -21,9 +21,7 @@ tmpfile=3D"${temp_dir}/tmp-perf.data" perfdatafile=3D"${temp_dir}/test-perf.data" outfile=3D"${temp_dir}/test-out.txt" errfile=3D"${temp_dir}/test-err.txt" -workload=3D"${temp_dir}/workload" awkscript=3D"${temp_dir}/awkscript" -jitdump_workload=3D"${temp_dir}/jitdump_workload" maxbrstack=3D"${temp_dir}/maxbrstack.py" =20 cleanup() @@ -60,37 +58,6 @@ perf_record_no_bpf() perf record --no-bpf-event "$@" } =20 -have_workload=3Dfalse -cat << _end_of_file_ | /usr/bin/cc -o "${workload}" -xc - -pthread && have= _workload=3Dtrue -#include -#include - -void work(void) { - struct timespec tm =3D { - .tv_nsec =3D 1000000, - }; - int i; - - /* Run for about 30 seconds */ - for (i =3D 0; i < 30000; i++) - nanosleep(&tm, NULL); -} - -void *threadfunc(void *arg) { - work(); - return NULL; -} - -int main(void) { - pthread_t th; - - pthread_create(&th, NULL, threadfunc, NULL); - work(); - pthread_join(th, NULL); - return 0; -} -_end_of_file_ - can_cpu_wide() { echo "Checking for CPU-wide recording on CPU $1" @@ -145,11 +112,6 @@ test_per_thread() =20 echo "--- Test per-thread ${desc}recording ---" =20 - if ! $have_workload ; then - echo "No workload, so skipping" - return 2 - fi - if [ "${k}" =3D "k" ] ; then can_kernel || return 2 fi @@ -252,9 +214,9 @@ test_per_thread() } _end_of_file_ =20 - $workload & + perf test -w thloop 30 2 & w1=3D$! - $workload & + perf test -w thloop 30 2 & w2=3D$! echo "Workload PIDs are $w1 and $w2" wait_for_threads ${w1} 2 @@ -283,139 +245,14 @@ test_jitdump() { echo "--- Test tracing self-modifying code that uses jitdump ---" =20 - script_path=3D$(realpath "$0") - script_dir=3D$(dirname "$script_path") - jitdump_incl_dir=3D"${script_dir}/../../util" - jitdump_h=3D"${jitdump_incl_dir}/jitdump.h" - if ! perf check feature -q libelf ; then echo "SKIP: libelf is needed for jitdump" return 2 fi =20 - if [ ! -e "${jitdump_h}" ] ; then - echo "SKIP: Include file jitdump.h not found" - return 2 - fi - - if [ -z "${have_jitdump_workload}" ] ; then - have_jitdump_workload=3Dfalse - # Create a workload that uses self-modifying code and generates its own = jitdump file - cat <<- "_end_of_file_" | /usr/bin/cc -o "${jitdump_workload}" -I "${jit= dump_incl_dir}" -xc - -pthread && have_jitdump_workload=3Dtrue - #define _GNU_SOURCE - #include - #include - #include - #include - #include - #include - #include - - #include "jitdump.h" - - #define CHK_BYTE 0x5a - - static inline uint64_t rdtsc(void) - { - unsigned int low, high; - - asm volatile("rdtsc" : "=3Da" (low), "=3Dd" (high)); - - return low | ((uint64_t)high) << 32; - } - - static FILE *open_jitdump(void) - { - struct jitheader header =3D { - .magic =3D JITHEADER_MAGIC, - .version =3D JITHEADER_VERSION, - .total_size =3D sizeof(header), - .pid =3D getpid(), - .timestamp =3D rdtsc(), - .flags =3D JITDUMP_FLAGS_ARCH_TIMESTAMP, - }; - char filename[256]; - FILE *f; - void *m; - - snprintf(filename, sizeof(filename), "jit-%d.dump", getpid()); - f =3D fopen(filename, "w+"); - if (!f) - goto err; - /* Create an MMAP event for the jitdump file. That is how perf tool fin= ds it. */ - m =3D mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(f), 0); - if (m =3D=3D MAP_FAILED) - goto err_close; - munmap(m, 4096); - if (fwrite(&header,sizeof(header),1,f) !=3D 1) - goto err_close; - return f; - - err_close: - fclose(f); - err: - return NULL; - } - - static int write_jitdump(FILE *f, void *addr, const uint8_t *dat, size_t= sz, uint64_t *idx) - { - struct jr_code_load rec =3D { - .p.id =3D JIT_CODE_LOAD, - .p.total_size =3D sizeof(rec) + sz, - .p.timestamp =3D rdtsc(), - .pid =3D getpid(), - .tid =3D gettid(), - .vma =3D (unsigned long)addr, - .code_addr =3D (unsigned long)addr, - .code_size =3D sz, - .code_index =3D ++*idx, - }; - - if (fwrite(&rec,sizeof(rec),1,f) !=3D 1 || - fwrite(dat, sz, 1, f) !=3D 1) - return -1; - return 0; - } - - static void close_jitdump(FILE *f) - { - fclose(f); - } - - int main() - { - /* Get a memory page to store executable code */ - void *addr =3D mmap(0, 4096, PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MA= P_PRIVATE, -1, 0); - /* Code to execute: mov CHK_BYTE, %eax ; ret */ - uint8_t dat[] =3D {0xb8, CHK_BYTE, 0x00, 0x00, 0x00, 0xc3}; - FILE *f =3D open_jitdump(); - uint64_t idx =3D 0; - int ret =3D 1; - - if (!f) - return 1; - /* Copy executable code to executable memory page */ - memcpy(addr, dat, sizeof(dat)); - /* Record it in the jitdump file */ - if (write_jitdump(f, addr, dat, sizeof(dat), &idx)) - goto out_close; - /* Call it */ - ret =3D ((int (*)(void))addr)() - CHK_BYTE; - out_close: - close_jitdump(f); - return ret; - } - _end_of_file_ - fi - - if ! $have_jitdump_workload ; then - echo "SKIP: No jitdump workload" - return 2 - fi - # Change to temp_dir so jitdump collateral files go there cd "${temp_dir}" - perf_record_no_bpf -o "${tmpfile}" -e intel_pt//u "${jitdump_workload}" + perf_record_no_bpf -o "${tmpfile}" -e intel_pt//u perf test -w jitdump perf inject -i "${tmpfile}" -o "${perfdatafile}" --jit decode_br_cnt=3D$(perf script -i "${perfdatafile}" --itrace=3Db | wc -l) # Note that overflow and lost errors are suppressed for the error count diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 9bcf1dbb0663..bf8ff7d54727 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -244,6 +244,7 @@ DECLARE_WORKLOAD(datasym); DECLARE_WORKLOAD(landlock); DECLARE_WORKLOAD(traploop); DECLARE_WORKLOAD(inlineloop); +DECLARE_WORKLOAD(jitdump); =20 #ifdef HAVE_RUST_SUPPORT DECLARE_WORKLOAD(code_with_type); diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/= Build index 2ef97f7affce..0eb6d99528eb 100644 --- a/tools/perf/tests/workloads/Build +++ b/tools/perf/tests/workloads/Build @@ -9,6 +9,7 @@ perf-test-y +=3D datasym.o perf-test-y +=3D landlock.o perf-test-y +=3D traploop.o perf-test-y +=3D inlineloop.o +perf-test-y +=3D jitdump.o =20 ifeq ($(CONFIG_RUST_SUPPORT),y) perf-test-y +=3D code_with_type.o diff --git a/tools/perf/tests/workloads/jitdump.c b/tools/perf/tests/worklo= ads/jitdump.c new file mode 100644 index 000000000000..6bbe703c8409 --- /dev/null +++ b/tools/perf/tests/workloads/jitdump.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "util/jitdump.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../tests.h" + +#ifndef HAVE_GETTID +#include +static inline pid_t gettid(void) +{ + return (pid_t)syscall(SYS_gettid); +} +#endif + +#define CHK_BYTE 0x5a + +static inline uint64_t get_timestamp(void) +{ +#if defined(__x86_64__) || defined(__i386__) + unsigned int low, high; + + asm volatile("rdtsc" : "=3Da"(low), "=3Dd"(high)); + + return low | ((uint64_t)high) << 32; +#else + struct timespec ts; + int ret; + + ret =3D clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret) + return 0; + + return ((uint64_t)ts.tv_sec * 1000000000) + ts.tv_nsec; +#endif +} + +static FILE *open_jitdump(void) +{ + struct jitheader header =3D { + .magic =3D JITHEADER_MAGIC, + .version =3D JITHEADER_VERSION, + .total_size =3D sizeof(header), + .pid =3D getpid(), + .timestamp =3D get_timestamp(), + .flags =3D +#if defined(__x86_64__) || defined(__i386__) + JITDUMP_FLAGS_ARCH_TIMESTAMP, +#else + 0, +#endif + }; + char filename[256]; + int fd; + FILE *f; + void *m; + + snprintf(filename, sizeof(filename), "jit-%d.dump", getpid()); + /* Securely open using O_CREAT | O_EXCL to prevent symlink attacks. */ + fd =3D open(filename, O_CREAT | O_EXCL | O_RDWR, 0644); + if (fd < 0) { + pr_err("Failed to open jitdump '%s': %s\n", filename, strerror(errno)); + return NULL; + } + f =3D fdopen(fd, "w+"); + if (!f) { + pr_err("Failed to associate stream with fd for '%s'\n", filename); + close(fd); + unlink(filename); + return NULL; + } + /* Create an MMAP event for the jitdump file. That is how perf tool finds= it. */ + m =3D mmap(0, getpagesize(), PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(f= ), 0); + if (m =3D=3D MAP_FAILED) { + pr_err("mmap failed: %s\n", strerror(errno)); + fclose(f); + unlink(filename); + return NULL; + } + munmap(m, getpagesize()); + + if (fwrite(&header, sizeof(header), 1, f) !=3D 1) { + pr_err("Error writing jitdump header\n"); + fclose(f); + unlink(filename); + return NULL; + } + return f; +} + +static int write_jitdump(FILE *f, void *addr, const void *dat, size_t sz, = uint64_t *idx) +{ + const char *sym =3D "jit_workload"; + size_t sym_len =3D strlen(sym) + 1; + struct jr_code_load rec =3D { + .p.id =3D JIT_CODE_LOAD, + .p.total_size =3D sizeof(rec) + sym_len + sz, + .p.timestamp =3D get_timestamp(), + .pid =3D getpid(), + .tid =3D gettid(), + .vma =3D (unsigned long)addr, + .code_addr =3D (unsigned long)addr, + .code_size =3D sz, + .code_index =3D ++*idx, + }; + + if (fwrite(&rec, sizeof(rec), 1, f) !=3D 1 || + fwrite(sym, sym_len, 1, f) !=3D 1 || + fwrite(dat, sz, 1, f) !=3D 1) + return -1; + return 0; +} + +static void close_jitdump(FILE *f) +{ + fclose(f); +} + +static int jitdump(int argc __maybe_unused, const char **argv __maybe_unus= ed) +{ +#if defined(__x86_64__) || defined(__i386__) + /* Code to execute: mov CHK_BYTE, %eax ; ret */ + uint8_t dat[] =3D { 0xb8, CHK_BYTE, 0x00, 0x00, 0x00, 0xc3 }; +#elif defined(__aarch64__) + /* Code to execute: mov w0, #CHK_BYTE ; ret */ + uint32_t dat[] =3D { 0x52800000 | (CHK_BYTE << 5), 0xd65f03c0 }; +#elif defined(__riscv) + /* Code to execute: li a0, CHK_BYTE ; ret */ + uint32_t dat[] =3D { ((CHK_BYTE & 0xfff) << 20) | 0x513, 0x00008067 }; +#elif defined(__powerpc__) + /* Code to execute: li r3, CHK_BYTE ; blr */ + uint32_t dat[] =3D { 0x38600000 | (CHK_BYTE & 0xffff), 0x4e800020 }; +#elif defined(__s390x__) + /* Code to execute: lhi %r2, CHK_BYTE ; br %r14 */ + uint8_t dat[] =3D { 0xa7, 0x28, (CHK_BYTE >> 8) & 0xff, CHK_BYTE & 0xff, = 0x07, 0xfe }; +#elif defined(__arm__) + /* Code to execute: mov r0, #CHK_BYTE ; bx lr */ + uint32_t dat[] =3D { 0xe3a00000 | (CHK_BYTE & 0xff), 0xe12fff1e }; +#elif defined(__mips__) + /* Code to execute: addiu $v0, $zero, CHK_BYTE ; jr $ra ; nop */ + uint32_t dat[] =3D { 0x24020000 | (CHK_BYTE & 0xffff), 0x03e00008, 0x0000= 0000 }; +#elif defined(__loongarch__) + /* Code to execute: addi.w $a0, $zero, CHK_BYTE ; jirl $zero, $ra, 0 */ + uint32_t dat[] =3D { 0x02800004 | ((CHK_BYTE & 0xfff) << 10), 0x4c000020 = }; +#else + uint32_t dat[0]; +#endif + void *addr; + FILE *f; + uint64_t idx =3D 0; + int ret =3D 1; + + /* Reachable fallback check for unsupported architectures right at start.= */ + if (sizeof(dat) =3D=3D 0) { + pr_err("JITDUMP workload not supported on this architecture\n"); + return 1; + } + + /* Get a memory page to store executable code. */ + addr =3D mmap(0, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (addr =3D=3D MAP_FAILED) { + pr_err("Failed to map 1 -rwx page\n"); + return 1; + } + + f =3D open_jitdump(); + if (!f) { + pr_err("Failed to open JITDUMP\n"); + munmap(addr, getpagesize()); + return 1; + } + /* Copy executable code to executable memory page. */ + memcpy(addr, dat, sizeof(dat)); + /* Synchronize the Instruction and Data caches. */ + __builtin___clear_cache(addr, (char *)addr + sizeof(dat)); + + /* Record it in the jitdump file */ + if (write_jitdump(f, addr, dat, sizeof(dat), &idx) =3D=3D 0) { + int (*fn)(void) =3D addr; + + /* Call the function. */ + ret =3D fn() - CHK_BYTE; + } + close_jitdump(f); + munmap(addr, getpagesize()); + return ret; +} + +DEFINE_WORKLOAD(jitdump); diff --git a/tools/perf/util/jitdump.h b/tools/perf/util/jitdump.h index ab2842def83d..f57bfebb20ff 100644 --- a/tools/perf/util/jitdump.h +++ b/tools/perf/util/jitdump.h @@ -11,9 +11,8 @@ #ifndef JITDUMP_H #define JITDUMP_H =20 -#include -#include #include +#include =20 /* JiTD */ #define JITHEADER_MAGIC 0x4A695444 --=20 2.54.0.929.g9b7fa37559-goog